diff --git a/.vscode/launch.json b/.vscode/launch.json
index ebce6d8..9926df6 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -5,12 +5,12 @@
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
- "name": ".NET Core Launch (console)",
+ "name": ".NET Core Launch (debug)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
- "program": "${workspaceFolder}/ObservatoryCore/bin/Debug/net5.0/ObservatoryCore.dll",
+ "program": "${workspaceFolder}/ObservatoryCore/bin/debug/net6.0-windows/ObservatoryCore.exe",
"args": [],
"cwd": "${workspaceFolder}/ObservatoryCore",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
diff --git a/ObservatoryBotanist/ObservatoryBotanist.csproj b/ObservatoryBotanist/ObservatoryBotanist.csproj
index e89d165..b7dcb13 100644
--- a/ObservatoryBotanist/ObservatoryBotanist.csproj
+++ b/ObservatoryBotanist/ObservatoryBotanist.csproj
@@ -5,6 +5,7 @@
enable
false
ObservatoryKey.snk
+ Debug;Release;Portable
diff --git a/ObservatoryCore/NativeNotification/NativePopup.cs b/ObservatoryCore/NativeNotification/NativePopup.cs
index 2f4aed7..eff794e 100644
--- a/ObservatoryCore/NativeNotification/NativePopup.cs
+++ b/ObservatoryCore/NativeNotification/NativePopup.cs
@@ -15,25 +15,29 @@ namespace Observatory.NativeNotification
public Guid InvokeNativeNotification(NotificationArgs notificationArgs)
{
var notificationGuid = Guid.NewGuid();
- var notification = new NotificationForm()
+ Application.OpenForms[0].Invoke(() =>
{
- Guid = notificationGuid
- };
- notification.Show();
- notifications.Add(notificationGuid, notification);
-
- //TODO: Implement winform notification
+ var notification = new NotificationForm(notificationGuid, notificationArgs);
+ notification.FormClosed += NotifyWindow_Closed;
+
+ notifications.Add(notificationGuid, notification);
+ notification.Show();
+ });
+
return notificationGuid;
}
- private void NotifyWindow_Closed(object sender, EventArgs e)
+ private void NotifyWindow_Closed(object? sender, EventArgs e)
{
- var currentNotification = (NotificationForm)sender;
-
- if (notifications.ContainsKey(currentNotification.Guid))
+ if (sender != null)
{
- notifications.Remove(currentNotification.Guid);
+ var currentNotification = (NotificationForm)sender;
+
+ if (notifications.ContainsKey(currentNotification.Guid))
+ {
+ notifications.Remove(currentNotification.Guid);
+ }
}
}
@@ -49,8 +53,7 @@ namespace Observatory.NativeNotification
{
if (notifications.ContainsKey(guid))
{
- //TODO: Update notification content
- // notifications[guid].DataContext = new NotificationViewModel(notificationArgs);
+ notifications[guid].Update(notificationArgs);
}
}
diff --git a/ObservatoryCore/ObservatoryCore.cs b/ObservatoryCore/ObservatoryCore.cs
index f3f69cf..4abc1fc 100644
--- a/ObservatoryCore/ObservatoryCore.cs
+++ b/ObservatoryCore/ObservatoryCore.cs
@@ -1,4 +1,5 @@
using Observatory.PluginManagement;
+using Observatory.Utils;
using System.Reflection.PortableExecutable;
namespace Observatory
@@ -11,6 +12,8 @@ namespace Observatory
[STAThread]
static void Main(string[] args)
{
+ SettingsManager.Load();
+
if (args.Length > 0 && File.Exists(args[0]))
{
var fileInfo = new FileInfo(args[0]);
@@ -27,14 +30,14 @@ namespace Observatory
{
try
{
- Properties.Core.Default.Upgrade();
+ // Properties.Core.Default.Upgrade();
}
catch
{
// Silently ignore properties upgrade failure.
}
Properties.Core.Default.CoreVersion = version;
- Properties.Core.Default.Save();
+ SettingsManager.Save();
}
diff --git a/ObservatoryCore/ObservatoryCore.csproj b/ObservatoryCore/ObservatoryCore.csproj
index ae449bf..6bd0e88 100644
--- a/ObservatoryCore/ObservatoryCore.csproj
+++ b/ObservatoryCore/ObservatoryCore.csproj
@@ -7,6 +7,7 @@
true
enable
Observatory
+ Debug;Release;Portable
@@ -59,9 +60,9 @@
-
+
-
+
diff --git a/ObservatoryCore/PluginManagement/PluginCore.cs b/ObservatoryCore/PluginManagement/PluginCore.cs
index 6e696bb..97773c2 100644
--- a/ObservatoryCore/PluginManagement/PluginCore.cs
+++ b/ObservatoryCore/PluginManagement/PluginCore.cs
@@ -105,17 +105,22 @@ namespace Observatory.PluginManagement
public void AddGridItems(IObservatoryWorker worker, IEnumerable items)
{
- //TODO: Add to winform list
+ //TODO: Use better bulk handling here.
+ foreach (var item in items)
+ {
+ worker.PluginUI.DataGrid.Add(item);
+ }
}
public void ClearGrid(IObservatoryWorker worker, object templateItem)
{
- //TODO: Clear winform list
+ worker.PluginUI.DataGrid.Clear();
}
public void ExecuteOnUIThread(Action action)
{
- //TODO: Execute action
+ if (Application.OpenForms.Count > 0)
+ Application.OpenForms[0].Invoke(action);
}
public System.Net.Http.HttpClient HttpClient
@@ -140,7 +145,7 @@ namespace Observatory.PluginManagement
get
{
var context = new System.Diagnostics.StackFrame(1).GetMethod();
-
+#if DEBUG || RELEASE
string folderLocation = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
+ $"{Path.DirectorySeparatorChar}ObservatoryCore{Path.DirectorySeparatorChar}{context?.DeclaringType?.Assembly.GetName().Name}{Path.DirectorySeparatorChar}";
@@ -148,6 +153,11 @@ namespace Observatory.PluginManagement
Directory.CreateDirectory(folderLocation);
return folderLocation;
+#elif PORTABLE
+ string? observatoryLocation = System.Diagnostics.Process.GetCurrentProcess()?.MainModule?.FileName;
+ var obsDir = new FileInfo(observatoryLocation ?? String.Empty).DirectoryName;
+ return $"{obsDir}{Path.DirectorySeparatorChar}plugins{Path.DirectorySeparatorChar}{context?.DeclaringType?.Assembly.GetName().Name}-Data{Path.DirectorySeparatorChar}";
+#endif
}
}
diff --git a/ObservatoryCore/PluginManagement/PluginManager.cs b/ObservatoryCore/PluginManagement/PluginManager.cs
index 59cfa73..2df6441 100644
--- a/ObservatoryCore/PluginManagement/PluginManager.cs
+++ b/ObservatoryCore/PluginManagement/PluginManager.cs
@@ -30,7 +30,7 @@ namespace Observatory.PluginManagement
}
- public readonly List<(string error, string detail)> errorList;
+ public readonly List<(string error, string? detail)> errorList;
public readonly List pluginPanels;
public readonly List pluginTables;
public readonly List<(IObservatoryWorker plugin, PluginStatus signed)> workerPlugins;
@@ -181,21 +181,22 @@ namespace Observatory.PluginManagement
});
Properties.Core.Default.PluginSettings = newSettings;
- Properties.Core.Default.Save();
+ SettingsManager.Save();
}
- private static List<(string, string)> LoadPlugins(out List<(IObservatoryWorker plugin, PluginStatus signed)> observatoryWorkers, out List<(IObservatoryNotifier plugin, PluginStatus signed)> observatoryNotifiers)
+ private static List<(string, string?)> LoadPlugins(out List<(IObservatoryWorker plugin, PluginStatus signed)> observatoryWorkers, out List<(IObservatoryNotifier plugin, PluginStatus signed)> observatoryNotifiers)
{
observatoryWorkers = new();
observatoryNotifiers = new();
- var errorList = new List<(string, string)>();
+ var errorList = new List<(string, string?)>();
string pluginPath = $"{AppDomain.CurrentDomain.BaseDirectory}{Path.DirectorySeparatorChar}plugins";
-
- string ownExe = System.Reflection.Assembly.GetExecutingAssembly().Location;
+
+ string? ownExe = System.Diagnostics.Process.GetCurrentProcess()?.MainModule?.FileName;
FileSignatureInfo ownSig;
- using (var stream = File.OpenRead(ownExe))
+ // This will throw if ownExe is null, but that's an error condition regardless.
+ using (var stream = File.OpenRead(ownExe ?? String.Empty))
ownSig = FileSignatureInfo.GetFromFileStream(stream);
@@ -259,7 +260,7 @@ namespace Observatory.PluginManagement
if (response == DialogResult.OK)
{
Properties.Core.Default.UnsignedAllowed.Add(pluginHash);
- Properties.Core.Default.Save();
+ SettingsManager.Save();
}
else
{
@@ -322,16 +323,16 @@ namespace Observatory.PluginManagement
System.Runtime.Loader.AssemblyLoadContext.Default.Resolving += (context, name) => {
- if (name.Name.EndsWith("resources"))
+ if ((name?.Name?.EndsWith("resources")).GetValueOrDefault(false))
{
return null;
}
// Importing Observatory.Framework in the Explorer Lua scripts causes an attempt to reload
// the assembly, just hand it back the one we already have.
- if (name.Name.StartsWith("Observatory.Framework") || name.Name == "ObservatoryFramework")
+ if ((name?.Name?.StartsWith("Observatory.Framework")).GetValueOrDefault(false) || name?.Name == "ObservatoryFramework")
{
- return context.Assemblies.Where(a => a.FullName.Contains("ObservatoryFramework")).First();
+ return context.Assemblies.Where(a => (a.FullName?.Contains("ObservatoryFramework")).GetValueOrDefault(false)).First();
}
var foundDlls = Directory.GetFileSystemEntries(new FileInfo($"{AppDomain.CurrentDomain.BaseDirectory}{Path.DirectorySeparatorChar}plugins{Path.DirectorySeparatorChar}deps").FullName, name.Name + ".dll", SearchOption.TopDirectoryOnly);
@@ -340,7 +341,7 @@ namespace Observatory.PluginManagement
return context.LoadFromAssemblyPath(foundDlls[0]);
}
- if (name.Name != recursionGuard)
+ if (name.Name != recursionGuard && name.Name != null)
{
recursionGuard = name.Name;
return context.LoadFromAssemblyName(name);
@@ -361,37 +362,43 @@ namespace Observatory.PluginManagement
}
catch (ReflectionTypeLoadException ex)
{
- types = ex.Types.Where(t => t != null).ToArray();
+ types = ex.Types.OfType().ToArray();
}
catch
{
types = Array.Empty();
}
- var workerTypes = types.Where(t => t.IsAssignableTo(typeof(IObservatoryWorker)));
- foreach (var worker in workerTypes)
+ IEnumerable workerTypes = types.Where(t => t.IsAssignableTo(typeof(IObservatoryWorker)));
+ foreach (Type worker in workerTypes)
{
- ConstructorInfo constructor = worker.GetConstructor(Array.Empty());
- object instance = constructor.Invoke(Array.Empty());
- workers.Add((instance as IObservatoryWorker, pluginStatus));
- if (instance is IObservatoryNotifier)
+ ConstructorInfo? constructor = worker.GetConstructor(Array.Empty());
+ if (constructor != null)
{
- // This is also a notifier; add to the notifier list as well, so the work and notifier are
- // the same instance and can share state.
- notifiers.Add((instance as IObservatoryNotifier, pluginStatus));
+ object instance = constructor.Invoke(Array.Empty());
+ workers.Add(((instance as IObservatoryWorker)!, pluginStatus));
+ if (instance is IObservatoryNotifier)
+ {
+ // This is also a notifier; add to the notifier list as well, so the work and notifier are
+ // the same instance and can share state.
+ notifiers.Add(((instance as IObservatoryNotifier)!, pluginStatus));
+ }
+ pluginCount++;
}
- pluginCount++;
}
// Filter out items which are also workers as we've already created them above.
var notifyTypes = types.Where(t =>
t.IsAssignableTo(typeof(IObservatoryNotifier)) && !t.IsAssignableTo(typeof(IObservatoryWorker)));
- foreach (var notifier in notifyTypes)
+ foreach (Type notifier in notifyTypes)
{
- ConstructorInfo constructor = notifier.GetConstructor(Array.Empty());
- object instance = constructor.Invoke(Array.Empty());
- notifiers.Add((instance as IObservatoryNotifier, PluginStatus.Signed));
- pluginCount++;
+ ConstructorInfo? constructor = notifier.GetConstructor(Array.Empty());
+ if (constructor != null)
+ {
+ object instance = constructor.Invoke(Array.Empty());
+ notifiers.Add(((instance as IObservatoryNotifier)!, PluginStatus.Signed));
+ pluginCount++;
+ }
}
if (pluginCount == 0)
diff --git a/ObservatoryCore/UI/CoreForm.Designer.cs b/ObservatoryCore/UI/CoreForm.Designer.cs
index 785e87f..5258cbf 100644
--- a/ObservatoryCore/UI/CoreForm.Designer.cs
+++ b/ObservatoryCore/UI/CoreForm.Designer.cs
@@ -360,6 +360,7 @@
this.TestButton.TabIndex = 12;
this.TestButton.Text = "Test";
this.TestButton.UseVisualStyleBackColor = false;
+ this.TestButton.Click += new System.EventHandler(this.TestButton_Click);
//
// ColourButton
//
@@ -569,6 +570,7 @@
this.ToggleMonitorButton.TabIndex = 3;
this.ToggleMonitorButton.Text = "Start Monitor";
this.ToggleMonitorButton.UseVisualStyleBackColor = false;
+ this.ToggleMonitorButton.Click += new System.EventHandler(this.ToggleMonitorButton_Click);
//
// ClearButton
//
diff --git a/ObservatoryCore/UI/CoreForm.Plugins.cs b/ObservatoryCore/UI/CoreForm.Plugins.cs
new file mode 100644
index 0000000..6b76cfa
--- /dev/null
+++ b/ObservatoryCore/UI/CoreForm.Plugins.cs
@@ -0,0 +1,109 @@
+using Observatory.PluginManagement;
+using Observatory.Framework.Interfaces;
+
+namespace Observatory.UI
+{
+ partial class CoreForm
+ {
+
+ private void PopulatePluginList()
+ {
+ List uniquePlugins = new();
+
+ foreach (var (plugin, signed) in PluginManager.GetInstance.workerPlugins)
+ {
+ if (!uniquePlugins.Contains(plugin))
+ {
+ uniquePlugins.Add(plugin);
+ ListViewItem item = new ListViewItem(new[] { plugin.Name, "Worker", plugin.Version, PluginStatusString(signed) });
+ PluginList.Items.Add(item);
+ }
+ }
+
+ foreach (var (plugin, signed) in PluginManager.GetInstance.notifyPlugins)
+ {
+ if (!uniquePlugins.Contains(plugin))
+ {
+ uniquePlugins.Add(plugin);
+ ListViewItem item = new ListViewItem(new[] { plugin.Name, "Notifier", plugin.Version, PluginStatusString(signed) });
+ PluginList.Items.Add(item);
+ }
+ }
+ }
+
+ private static string PluginStatusString(PluginManager.PluginStatus status)
+ {
+ switch (status)
+ {
+ case PluginManager.PluginStatus.Signed:
+ return "Signed";
+
+ case PluginManager.PluginStatus.Unsigned:
+ return "Unsigned";
+
+ case PluginManager.PluginStatus.InvalidSignature:
+ return "Invalid Signature";
+
+ case PluginManager.PluginStatus.InvalidPlugin:
+ return "Invalid Plugin";
+
+ case PluginManager.PluginStatus.InvalidLibrary:
+ return "Invalid File";
+
+ case PluginManager.PluginStatus.NoCert:
+ return "Unsigned Observatory (Debug build)";
+
+ case PluginManager.PluginStatus.SigCheckDisabled:
+ return "Signature Checks Disabled";
+
+ default:
+ return string.Empty;
+ }
+ }
+
+ private void CreatePluginTabs()
+ {
+ var uiPlugins = PluginManager.GetInstance.workerPlugins.Where(p => p.plugin.PluginUI.PluginUIType != Framework.PluginUI.UIType.None);
+
+ PluginHelper.CreatePluginTabs(CoreMenu, uiPlugins, uiPanels);
+
+ foreach(ToolStripMenuItem item in CoreMenu.Items)
+ {
+ pluginList.Add(item.Text, item);
+ }
+ }
+
+ private void CreatePluginSettings()
+ {
+ foreach (var plugin in PluginManager.GetInstance.workerPlugins)
+ {
+ var pluginSettingsPanel = new SettingsPanel(plugin.plugin, AdjustPanelsBelow);
+ AddSettingsPanel(pluginSettingsPanel);
+ }
+ foreach (var plugin in PluginManager.GetInstance.notifyPlugins)
+ {
+ var pluginSettingsPanel = new SettingsPanel(plugin.plugin, AdjustPanelsBelow);
+ AddSettingsPanel(pluginSettingsPanel);
+ }
+ }
+
+ private void AddSettingsPanel(SettingsPanel panel)
+ {
+ int lowestPoint = 0;
+ foreach (Control control in CorePanel.Controls)
+ {
+ if (control.Location.Y + control.Height > lowestPoint)
+ lowestPoint = control.Location.Y + control.Height;
+ }
+ DuplicateControlVisuals(PopupNotificationLabel, panel.Header);
+ panel.Header.TextAlign = PopupNotificationLabel.TextAlign;
+ panel.Header.Location = new Point(PopupNotificationLabel.Location.X, lowestPoint);
+
+ DuplicateControlVisuals(PopupSettingsPanel, panel, false);
+ panel.Location = new Point(PopupSettingsPanel.Location.X, lowestPoint + panel.Header.Height);
+ panel.Visible = false;
+ CorePanel.Controls.Add(panel.Header);
+ CorePanel.Controls.Add(panel);
+ }
+ }
+}
\ No newline at end of file
diff --git a/ObservatoryCore/UI/CoreForm.Settings.cs b/ObservatoryCore/UI/CoreForm.Settings.cs
new file mode 100644
index 0000000..180877e
--- /dev/null
+++ b/ObservatoryCore/UI/CoreForm.Settings.cs
@@ -0,0 +1,111 @@
+using Observatory.Utils;
+
+namespace Observatory.UI
+{
+ partial class CoreForm
+ {
+ private void ColourButton_Click(object _, EventArgs e)
+ {
+ var selectionResult = PopupColour.ShowDialog();
+ if (selectionResult == DialogResult.OK)
+ {
+ ColourButton.BackColor = PopupColour.Color;
+ Properties.Core.Default.NativeNotifyColour = (uint)PopupColour.Color.ToArgb();
+ SettingsManager.Save();
+ }
+ }
+
+ private void PopupCheckbox_CheckedChanged(object _, EventArgs e)
+ {
+ Properties.Core.Default.NativeNotify = PopupCheckbox.Checked;
+ SettingsManager.Save();
+ }
+
+ private void DurationSpinner_ValueChanged(object _, EventArgs e)
+ {
+ Properties.Core.Default.NativeNotifyTimeout = (int)DurationSpinner.Value;
+ SettingsManager.Save();
+ }
+
+ private void ScaleSpinner_ValueChanged(object _, EventArgs e)
+ {
+ Properties.Core.Default.NativeNotifyScale = (int)ScaleSpinner.Value;
+ SettingsManager.Save();
+ }
+
+ private void FontDropdown_SelectedIndexChanged(object _, EventArgs e)
+ {
+ Properties.Core.Default.NativeNotifyFont = FontDropdown.SelectedItem.ToString();
+ SettingsManager.Save();
+ }
+
+ private void CornerDropdown_SelectedIndexChanged(object _, EventArgs e)
+ {
+ Properties.Core.Default.NativeNotifyCorner = CornerDropdown.SelectedIndex;
+ SettingsManager.Save();
+ }
+
+ private void DisplayDropdown_SelectedIndexChanged(object _, EventArgs e)
+ {
+ Properties.Core.Default.NativeNotifyScreen = DisplayDropdown.SelectedIndex - 1;
+ SettingsManager.Save();
+ }
+
+ private void VoiceVolumeSlider_Scroll(object _, EventArgs e)
+ {
+ Properties.Core.Default.VoiceVolume = VoiceVolumeSlider.Value;
+ SettingsManager.Save();
+ }
+
+ private void VoiceSpeedSlider_Scroll(object _, EventArgs e)
+ {
+ Properties.Core.Default.VoiceRate = VoiceSpeedSlider.Value;
+ SettingsManager.Save();
+ }
+
+ private void VoiceCheckbox_CheckedChanged(object _, EventArgs e)
+ {
+ Properties.Core.Default.VoiceNotify = VoiceCheckbox.Checked;
+ SettingsManager.Save();
+ }
+
+ private void VoiceDropdown_SelectedIndexChanged(object _, EventArgs e)
+ {
+ Properties.Core.Default.VoiceSelected = VoiceDropdown.SelectedItem.ToString();
+ SettingsManager.Save();
+ }
+
+ private void PopulateDropdownOptions()
+ {
+ var fonts = new System.Drawing.Text.InstalledFontCollection().Families;
+ FontDropdown.Items.AddRange(fonts.Select(f => f.Name).ToArray());
+
+ DisplayDropdown.Items.Add("Primary");
+ if (Screen.AllScreens.Length > 1)
+ for (int i = 0; i < Screen.AllScreens.Length; i++)
+ DisplayDropdown.Items.Add((i + 1).ToString());
+
+ var voices = new System.Speech.Synthesis.SpeechSynthesizer().GetInstalledVoices();
+ foreach (var voice in voices.Select(v => v.VoiceInfo.Name))
+ VoiceDropdown.Items.Add(voice);
+
+ }
+
+ private void PopulateNativeSettings()
+ {
+ var settings = Properties.Core.Default;
+
+ DisplayDropdown.SelectedIndex = settings.NativeNotifyScreen + 1;
+ CornerDropdown.SelectedIndex = settings.NativeNotifyCorner;
+ FontDropdown.SelectedItem = settings.NativeNotifyFont;
+ ScaleSpinner.Value = settings.NativeNotifyScale;
+ DurationSpinner.Value = settings.NativeNotifyTimeout;
+ ColourButton.BackColor = Color.FromArgb((int)settings.NativeNotifyColour);
+ PopupCheckbox.Checked = settings.NativeNotify;
+ VoiceVolumeSlider.Value = settings.VoiceVolume;
+ VoiceSpeedSlider.Value = settings.VoiceRate;
+ VoiceDropdown.SelectedItem = settings.VoiceSelected;
+ VoiceCheckbox.Checked = settings.VoiceNotify;
+ }
+ }
+}
\ No newline at end of file
diff --git a/ObservatoryCore/UI/CoreForm.cs b/ObservatoryCore/UI/CoreForm.cs
index e3da322..03aefe9 100644
--- a/ObservatoryCore/UI/CoreForm.cs
+++ b/ObservatoryCore/UI/CoreForm.cs
@@ -1,6 +1,9 @@
-using Observatory.Framework.Interfaces;
+using Observatory.Framework;
+using Observatory.Framework.Interfaces;
using Observatory.PluginManagement;
using Observatory.Utils;
+using System.Text;
+using System.Windows.Forms;
namespace Observatory.UI
{
@@ -37,40 +40,6 @@ namespace Observatory.UI
AdjustPanelsBelow(PopupSettingsPanel, AdjustmentDirection.Up);
}
- private void PopulateDropdownOptions()
- {
- var fonts = new System.Drawing.Text.InstalledFontCollection().Families;
- FontDropdown.Items.AddRange(fonts.Select(f => f.Name).ToArray());
-
- DisplayDropdown.Items.Add("Primary");
- if (Screen.AllScreens.Length > 1)
- for (int i = 0; i < Screen.AllScreens.Length; i++)
- DisplayDropdown.Items.Add((i + 1).ToString());
-
- var voices = new System.Speech.Synthesis.SpeechSynthesizer().GetInstalledVoices();
- foreach (var voice in voices.Select(v => v.VoiceInfo.Name))
- VoiceDropdown.Items.Add(voice);
-
- }
-
- private void PopulateNativeSettings()
- {
- var settings = Properties.Core.Default;
-
- DisplayDropdown.SelectedIndex = settings.NativeNotifyScreen + 1;
- CornerDropdown.SelectedIndex = settings.NativeNotifyCorner;
- FontDropdown.SelectedItem = settings.NativeNotifyFont;
- ScaleSpinner.Value = settings.NativeNotifyScale;
- DurationSpinner.Value = settings.NativeNotifyTimeout;
- ColourButton.BackColor = Color.FromArgb((int)settings.NativeNotifyColour);
- PopupCheckbox.Checked = settings.NativeNotify;
- VoiceVolumeSlider.Value = settings.VoiceVolume;
- VoiceSpeedSlider.Value = settings.VoiceRate;
- VoiceDropdown.SelectedItem = settings.VoiceSelected;
- VoiceCheckbox.Checked = settings.VoiceNotify;
- }
-
-
private void CoreMenu_SizeChanged(object? sender, EventArgs e)
{
CorePanel.Location = new Point(12 + CoreMenu.Width, 12);
@@ -80,95 +49,27 @@ namespace Observatory.UI
private Dictionary pluginList;
- private void CreatePluginTabs()
+ private static void DuplicateControlVisuals(Control source, Control target, bool applyHeight = true)
{
- var uiPlugins = PluginManager.GetInstance.workerPlugins.Where(p => p.plugin.PluginUI.PluginUIType != Framework.PluginUI.UIType.None);
-
- PluginHelper.CreatePluginTabs(CoreMenu, uiPlugins, uiPanels);
-
- foreach(ToolStripMenuItem item in CoreMenu.Items)
- {
- pluginList.Add(item.Text, item);
- }
+ if (applyHeight) target.Height = source.Height;
+ target.Width = source.Width;
+ target.Font = source.Font;
+ target.ForeColor = source.ForeColor;
+ target.BackColor = source.BackColor;
+ target.Anchor = source.Anchor;
}
- private void CreatePluginSettings()
+ private void ToggleMonitorButton_Click(object sender, EventArgs e)
{
- foreach (var plugin in PluginManager.GetInstance.workerPlugins)
+ if ((LogMonitor.GetInstance.CurrentState & Framework.LogMonitorState.Realtime) == Framework.LogMonitorState.Realtime)
{
- var pluginSettingsPanel = new SettingsPanel(plugin.plugin, AdjustPanelsBelow);
- AddSettingsPanel(pluginSettingsPanel);
+ LogMonitor.GetInstance.Stop();
+ ToggleMonitorButton.Text = "Start Monitor";
}
- foreach (var plugin in PluginManager.GetInstance.notifyPlugins)
+ else
{
- var pluginSettingsPanel = new SettingsPanel(plugin.plugin, AdjustPanelsBelow);
- AddSettingsPanel(pluginSettingsPanel);
- }
- }
-
- private void AddSettingsPanel(SettingsPanel panel)
- {
- int lowestPoint = 0;
- foreach (Control control in CorePanel.Controls)
- {
- if (control.Location.Y + control.Height > lowestPoint)
- lowestPoint = control.Location.Y + control.Height;
- }
- panel.Header.Location = new Point(PopupNotificationLabel.Location.X, lowestPoint);
- panel.Header.Width = PopupNotificationLabel.Width;
- panel.Header.Font = PopupNotificationLabel.Font;
- panel.Header.ForeColor = PopupNotificationLabel.ForeColor;
- panel.Header.BackColor = PopupNotificationLabel.BackColor;
- panel.Header.TextAlign = PopupNotificationLabel.TextAlign;
- panel.Location = new Point(PopupNotificationLabel.Location.X, lowestPoint + panel.Header.Height);
- panel.Width = PopupSettingsPanel.Width;
- CorePanel.Controls.Add(panel.Header);
- CorePanel.Controls.Add(panel);
- }
-
- private void PopulatePluginList()
- {
- List uniquePlugins = new();
-
-
- foreach (var (plugin, signed) in PluginManager.GetInstance.workerPlugins)
- {
- if (!uniquePlugins.Contains(plugin))
- {
- uniquePlugins.Add(plugin);
- ListViewItem item = new ListViewItem(new[] { plugin.Name, "Worker", plugin.Version, PluginStatusString(signed) });
- PluginList.Items.Add(item);
- }
- }
- }
-
- private static string PluginStatusString(PluginManager.PluginStatus status)
- {
- switch (status)
- {
- case PluginManager.PluginStatus.Signed:
- return "Signed";
-
- case PluginManager.PluginStatus.Unsigned:
- return "Unsigned";
-
- case PluginManager.PluginStatus.InvalidSignature:
- return "Invalid Signature";
-
- case PluginManager.PluginStatus.InvalidPlugin:
- return "Invalid Plugin";
-
- case PluginManager.PluginStatus.InvalidLibrary:
- return "Invalid File";
-
- case PluginManager.PluginStatus.NoCert:
- return "Unsigned Observatory (Debug build)";
-
- case PluginManager.PluginStatus.SigCheckDisabled:
- return "Signature Checks Disabled";
-
- default:
- return string.Empty;
+ LogMonitor.GetInstance.Start();
+ ToggleMonitorButton.Text = "Stop Monitor";
}
}
@@ -337,82 +238,16 @@ namespace Observatory.UI
Up, Down
}
- #region Settings Changes
-
- private void ColourButton_Click(object _, EventArgs e)
+ private void TestButton_Click(object sender, EventArgs e)
{
- var selectionResult = PopupColour.ShowDialog();
- if (selectionResult == DialogResult.OK)
+ NotificationArgs args = new()
{
- ColourButton.BackColor = PopupColour.Color;
- Properties.Core.Default.NativeNotifyColour = (uint)PopupColour.Color.ToArgb();
- Properties.Core.Default.Save();
- }
+ Title = "Test Notification",
+ Detail = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec at elit maximus, ornare dui nec, accumsan velit. Vestibulum fringilla elit."
+ };
+ var testNotify = new NotificationForm(new Guid(), args);
+ testNotify.Show();
+
}
-
- private void PopupCheckbox_CheckedChanged(object _, EventArgs e)
- {
- Properties.Core.Default.NativeNotify = PopupCheckbox.Checked;
- Properties.Core.Default.Save();
- }
-
- private void DurationSpinner_ValueChanged(object _, EventArgs e)
- {
- Properties.Core.Default.NativeNotifyTimeout = (int)DurationSpinner.Value;
- Properties.Core.Default.Save();
- }
-
- private void ScaleSpinner_ValueChanged(object _, EventArgs e)
- {
- Properties.Core.Default.NativeNotifyScale = (int)ScaleSpinner.Value;
- Properties.Core.Default.Save();
- }
-
- private void FontDropdown_SelectedIndexChanged(object _, EventArgs e)
- {
- Properties.Core.Default.NativeNotifyFont = FontDropdown.SelectedItem.ToString();
- Properties.Core.Default.Save();
- }
-
- private void CornerDropdown_SelectedIndexChanged(object _, EventArgs e)
- {
- Properties.Core.Default.NativeNotifyCorner = CornerDropdown.SelectedIndex;
- Properties.Core.Default.Save();
- }
-
- private void DisplayDropdown_SelectedIndexChanged(object _, EventArgs e)
- {
- Properties.Core.Default.NativeNotifyScreen = DisplayDropdown.SelectedIndex - 1;
- Properties.Core.Default.Save();
- }
-
- private void VoiceVolumeSlider_Scroll(object _, EventArgs e)
- {
- Properties.Core.Default.VoiceVolume = VoiceVolumeSlider.Value;
- Properties.Core.Default.Save();
- }
-
- private void VoiceSpeedSlider_Scroll(object _, EventArgs e)
- {
- Properties.Core.Default.VoiceRate = VoiceSpeedSlider.Value;
- Properties.Core.Default.Save();
- }
-
- private void VoiceCheckbox_CheckedChanged(object _, EventArgs e)
- {
- Properties.Core.Default.VoiceNotify = VoiceCheckbox.Checked;
- Properties.Core.Default.Save();
- }
-
- private void VoiceDropdown_SelectedIndexChanged(object _, EventArgs e)
- {
- Properties.Core.Default.VoiceSelected = VoiceDropdown.SelectedItem.ToString();
- Properties.Core.Default.Save();
- }
-
-
- #endregion
-
-
}
}
\ No newline at end of file
diff --git a/ObservatoryCore/UI/DefaultSorter.cs b/ObservatoryCore/UI/DefaultSorter.cs
index 040859d..be77f8a 100644
--- a/ObservatoryCore/UI/DefaultSorter.cs
+++ b/ObservatoryCore/UI/DefaultSorter.cs
@@ -3,10 +3,11 @@ using System.Collections;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using Observatory.Framework.Interfaces;
namespace Observatory.UI
{
- internal class DefaultSorter : IComparer
+ internal class DefaultSorter : IObservatoryComparer
{
///
/// Specifies the column to be sorted
@@ -15,7 +16,7 @@ namespace Observatory.UI
///
/// Specifies the order in which to sort (i.e. 'Ascending').
///
- private SortOrder OrderOfSort;
+ private int OrderOfSort;
///
/// Case insensitive comparer object
///
@@ -30,7 +31,7 @@ namespace Observatory.UI
ColumnToSort = 0;
// Initialize the sort order to 'none'
- OrderOfSort = SortOrder.None;
+ OrderOfSort = 0;
// Initialize the CaseInsensitiveComparer object
ObjectCompare = new CaseInsensitiveComparer();
@@ -49,25 +50,68 @@ namespace Observatory.UI
ListViewItem? listviewX = (ListViewItem?)x;
ListViewItem? listviewY = (ListViewItem?)y;
+ if (OrderOfSort == 0)
+ return 0;
+
// Compare the two items
- compareResult = ObjectCompare.Compare(listviewX?.SubItems[ColumnToSort].Text, listviewY?.SubItems[ColumnToSort].Text);
+ compareResult = NaturalCompare(listviewX?.SubItems[ColumnToSort].Text, listviewY?.SubItems[ColumnToSort].Text);
// Calculate correct return value based on object comparison
- if (OrderOfSort == SortOrder.Ascending)
+ if (OrderOfSort == 1)
{
// Ascending sort is selected, return normal result of compare operation
return compareResult;
}
- else if (OrderOfSort == SortOrder.Descending)
+ else
{
// Descending sort is selected, return negative result of compare operation
return (-compareResult);
}
- else
+ }
+
+ private static int NaturalCompare(string? x, string? y)
+ {
+ for (int i = 0; i <= x?.Length && i <= y?.Length; i++)
{
- // Return '0' to indicate they are equal
- return 0;
+ // If we've reached the end of the string without finding a difference
+ // the longer string is "greater".
+ if (i == x.Length || i == y.Length)
+ return x.Length > y.Length ? 1 : y.Length > x.Length ? -1 : 0;
+
+ // We've found a number in the same place in both strings.
+ if (Char.IsDigit(x[i]) && Char.IsDigit(y[i]))
+ {
+ // Walk ahead and get the full numbers.
+ string xNum = new(x[i..].TakeWhile(c => Char.IsDigit(c)).ToArray());
+ string yNum = new(y[i..].TakeWhile(c => Char.IsDigit(c)).ToArray());
+
+ // Pad with zeroes to equal lengths.
+ int numLength = Math.Max(xNum.Length, yNum.Length);
+ string xNumPadded = xNum.PadLeft(numLength, '0');
+ string yNumPadded = yNum.PadLeft(numLength, '0');
+
+ // Now that they're the same length a direct compare works.
+ int result = xNumPadded.CompareTo(yNumPadded);
+ if (result != 0)
+ {
+ return result;
+ }
+ else
+ {
+ // The numbers are identical, skip them and keep moving.
+ i += numLength - 1;
+ }
+ }
+ // Check if we have unequal letters.
+ else if (x[i] != y[i])
+ {
+ // Straight compare and return.
+ return x[i] > y[i] ? 1 : -1;
+ }
}
+
+ // If we somehow make it here, return equal result.
+ return 0;
}
///
@@ -88,7 +132,7 @@ namespace Observatory.UI
///
/// Gets or sets the order of sorting to apply (for example, 'Ascending' or 'Descending').
///
- public SortOrder Order
+ public int Order
{
set
{
diff --git a/ObservatoryCore/UI/DwmHelper.cs b/ObservatoryCore/UI/DwmHelper.cs
new file mode 100644
index 0000000..5724088
--- /dev/null
+++ b/ObservatoryCore/UI/DwmHelper.cs
@@ -0,0 +1,310 @@
+// Source: https://stackoverflow.com/questions/51578104/how-to-create-a-semi-transparent-or-blurred-backcolor-in-a-windows-form
+
+using System.Runtime.InteropServices;
+using System.Security;
+
+[SuppressUnmanagedCodeSecurity]
+public class DwmHelper
+{
+ public const int WM_DWMCOMPOSITIONCHANGED = 0x031E;
+
+ public struct MARGINS
+ {
+ public int leftWidth;
+ public int rightWidth;
+ public int topHeight;
+ public int bottomHeight;
+
+ public MARGINS(int LeftWidth, int RightWidth, int TopHeight, int BottomHeight)
+ {
+ leftWidth = LeftWidth;
+ rightWidth = RightWidth;
+ topHeight = TopHeight;
+ bottomHeight = BottomHeight;
+ }
+
+ public void NoMargins()
+ {
+ leftWidth = 0;
+ rightWidth = 0;
+ topHeight = 0;
+ bottomHeight = 0;
+ }
+
+ public void SheetOfGlass()
+ {
+ leftWidth = -1;
+ rightWidth = -1;
+ topHeight = -1;
+ bottomHeight = -1;
+ }
+ }
+
+ [Flags]
+ public enum DWM_BB
+ {
+ Enable = 1,
+ BlurRegion = 2,
+ TransitionOnMaximized = 4
+ }
+
+ // https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
+ public enum DWMWINDOWATTRIBUTE : uint
+ {
+ NCRenderingEnabled = 1, //Get atttribute
+ NCRenderingPolicy, //Enable or disable non-client rendering
+ TransitionsForceDisabled,
+ AllowNCPaint,
+ CaptionButtonBounds, //Get atttribute
+ NonClientRtlLayout,
+ ForceIconicRepresentation,
+ Flip3DPolicy,
+ ExtendedFrameBounds, //Get atttribute
+ HasIconicBitmap,
+ DisallowPeek,
+ ExcludedFromPeek,
+ Cloak,
+ Cloaked, //Get atttribute. Returns a DWMCLOACKEDREASON
+ FreezeRepresentation,
+ PassiveUpdateMode,
+ UseHostBackDropBrush,
+ AccentPolicy = 19, // Win 10 (undocumented)
+ ImmersiveDarkMode = 20, // Win 11 22000
+ WindowCornerPreference = 33, // Win 11 22000
+ BorderColor, // Win 11 22000
+ CaptionColor, // Win 11 22000
+ TextColor, // Win 11 22000
+ VisibleFrameBorderThickness, // Win 11 22000
+ SystemBackdropType // Win 11 22621
+ }
+
+ public enum DWMCLOACKEDREASON : uint
+ {
+ DWM_CLOAKED_APP = 0x0000001, //cloaked by its owner application.
+ DWM_CLOAKED_SHELL = 0x0000002, //cloaked by the Shell.
+ DWM_CLOAKED_INHERITED = 0x0000004 //inherited from its owner window.
+ }
+
+ public enum DWMNCRENDERINGPOLICY : uint
+ {
+ UseWindowStyle, // Enable/disable non-client rendering based on window style
+ Disabled, // Disabled non-client rendering; window style is ignored
+ Enabled, // Enabled non-client rendering; window style is ignored
+ };
+
+ public enum DWMACCENTSTATE
+ {
+ ACCENT_DISABLED = 0,
+ ACCENT_ENABLE_GRADIENT = 1,
+ ACCENT_ENABLE_TRANSPARENTGRADIENT = 2,
+ ACCENT_ENABLE_BLURBEHIND = 3,
+ ACCENT_INVALID_STATE = 4
+ }
+
+ [Flags]
+ public enum CompositionAction : uint
+ {
+ DWM_EC_DISABLECOMPOSITION = 0,
+ DWM_EC_ENABLECOMPOSITION = 1
+ }
+
+ // Values designating how Flip3D treats a given window.
+ enum DWMFLIP3DWINDOWPOLICY : uint
+ {
+ Default, // Hide or include the window in Flip3D based on window style and visibility.
+ ExcludeBelow, // Display the window under Flip3D and disabled.
+ ExcludeAbove, // Display the window above Flip3D and enabled.
+ };
+
+ public enum ThumbProperties_dwFlags : uint
+ {
+ RectDestination = 0x00000001,
+ RectSource = 0x00000002,
+ Opacity = 0x00000004,
+ Visible = 0x00000008,
+ SourceClientAreaOnly = 0x00000010
+ }
+
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct AccentPolicy
+ {
+ public DWMACCENTSTATE AccentState;
+ public int AccentFlags;
+ public int GradientColor;
+ public int AnimationId;
+
+ public AccentPolicy(DWMACCENTSTATE accentState, int accentFlags, int gradientColor, int animationId)
+ {
+ AccentState = accentState;
+ AccentFlags = accentFlags;
+ GradientColor = gradientColor;
+ AnimationId = animationId;
+ }
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct DWM_BLURBEHIND
+ {
+ public DWM_BB dwFlags;
+ public int fEnable;
+ public IntPtr hRgnBlur;
+ public int fTransitionOnMaximized;
+
+ public DWM_BLURBEHIND(bool enabled)
+ {
+ dwFlags = DWM_BB.Enable;
+ fEnable = (enabled) ? 1 : 0;
+ hRgnBlur = IntPtr.Zero;
+ fTransitionOnMaximized = 0;
+ }
+
+ public Region Region => Region.FromHrgn(hRgnBlur);
+
+ public bool TransitionOnMaximized
+ {
+ get => fTransitionOnMaximized > 0;
+ set
+ {
+ fTransitionOnMaximized = (value) ? 1 : 0;
+ dwFlags |= DWM_BB.TransitionOnMaximized;
+ }
+ }
+
+ public void SetRegion(Graphics graphics, Region region)
+ {
+ hRgnBlur = region.GetHrgn(graphics);
+ dwFlags |= DWM_BB.BlurRegion;
+ }
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct WinCompositionAttrData
+ {
+ public DWMWINDOWATTRIBUTE Attribute;
+ public IntPtr Data; //Will point to an AccentPolicy struct, where Attribute will be DWMWINDOWATTRIBUTE.AccentPolicy
+ public int SizeOfData;
+
+ public WinCompositionAttrData(DWMWINDOWATTRIBUTE attribute, IntPtr data, int sizeOfData)
+ {
+ Attribute = attribute;
+ Data = data;
+ SizeOfData = sizeOfData;
+ }
+ }
+
+ private static int GetBlurBehindPolicyAccentFlags()
+ {
+ int drawLeftBorder = 20;
+ int drawTopBorder = 40;
+ int drawRightBorder = 80;
+ int drawBottomBorder = 100;
+ return (drawLeftBorder | drawTopBorder | drawRightBorder | drawBottomBorder);
+ }
+
+ //https://msdn.microsoft.com/en-us/library/windows/desktop/aa969508(v=vs.85).aspx
+ [DllImport("dwmapi.dll")]
+ internal static extern int DwmEnableBlurBehindWindow(IntPtr hwnd, ref DWM_BLURBEHIND blurBehind);
+
+ [DllImport("dwmapi.dll", PreserveSig = false)]
+ public static extern void DwmEnableComposition(CompositionAction uCompositionAction);
+
+ //https://msdn.microsoft.com/it-it/library/windows/desktop/aa969512(v=vs.85).aspx
+ [DllImport("dwmapi.dll")]
+ internal static extern int DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS pMarInset);
+
+ //https://msdn.microsoft.com/en-us/library/windows/desktop/aa969515(v=vs.85).aspx
+ [DllImport("dwmapi.dll")]
+ internal static extern int DwmGetWindowAttribute(IntPtr hwnd, DWMWINDOWATTRIBUTE attr, ref int attrValue, int attrSize);
+
+ //https://msdn.microsoft.com/en-us/library/windows/desktop/aa969524(v=vs.85).aspx
+ [DllImport("dwmapi.dll")]
+ internal static extern int DwmSetWindowAttribute(IntPtr hwnd, DWMWINDOWATTRIBUTE attr, ref int attrValue, int attrSize);
+
+ [DllImport("User32.dll", SetLastError = true)]
+ internal static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WinCompositionAttrData data);
+
+ [DllImport("dwmapi.dll")]
+ internal static extern int DwmIsCompositionEnabled(ref int pfEnabled);
+
+ public static bool IsCompositionEnabled()
+ {
+ int pfEnabled = 0;
+ int result = DwmIsCompositionEnabled(ref pfEnabled);
+ return (pfEnabled == 1) ? true : false;
+ }
+
+ public static bool IsNonClientRenderingEnabled(IntPtr hWnd)
+ {
+ int gwaEnabled = 0;
+ int result = DwmGetWindowAttribute(hWnd, DWMWINDOWATTRIBUTE.NCRenderingEnabled, ref gwaEnabled, sizeof(int));
+ return gwaEnabled == 1;
+ }
+
+ public static bool WindowSetAttribute(IntPtr hWnd, DWMWINDOWATTRIBUTE attribute, int attributeValue)
+ {
+ int result = DwmSetWindowAttribute(hWnd, attribute, ref attributeValue, sizeof(int));
+ return (result == 0);
+ }
+
+ public static void Windows10EnableBlurBehind(IntPtr hWnd)
+ {
+ DWMNCRENDERINGPOLICY policy = DWMNCRENDERINGPOLICY.Enabled;
+ WindowSetAttribute(hWnd, DWMWINDOWATTRIBUTE.NCRenderingPolicy, (int)policy);
+
+ AccentPolicy accPolicy = new AccentPolicy()
+ {
+ AccentState = DWMACCENTSTATE.ACCENT_ENABLE_BLURBEHIND,
+ };
+
+ int accentSize = Marshal.SizeOf(accPolicy);
+ IntPtr accentPtr = Marshal.AllocHGlobal(accentSize);
+ Marshal.StructureToPtr(accPolicy, accentPtr, false);
+ var data = new WinCompositionAttrData(DWMWINDOWATTRIBUTE.AccentPolicy, accentPtr, accentSize);
+
+ SetWindowCompositionAttribute(hWnd, ref data);
+ Marshal.FreeHGlobal(accentPtr);
+ }
+
+ public static bool WindowEnableBlurBehind(IntPtr hWnd)
+ {
+ DWMNCRENDERINGPOLICY policy = DWMNCRENDERINGPOLICY.Enabled;
+ WindowSetAttribute(hWnd, DWMWINDOWATTRIBUTE.NCRenderingPolicy, (int)policy);
+
+ DWM_BLURBEHIND dwm_BB = new DWM_BLURBEHIND(true);
+ int result = DwmEnableBlurBehindWindow(hWnd, ref dwm_BB);
+ return result == 0;
+ }
+
+ public static bool WindowExtendIntoClientArea(IntPtr hWnd, MARGINS margins)
+ {
+ // Extend frame on the bottom of client area
+ int result = DwmExtendFrameIntoClientArea(hWnd, ref margins);
+ return result == 0;
+ }
+
+ public static bool WindowBorderlessDropShadow(IntPtr hWnd, int shadowSize)
+ {
+ MARGINS margins = new MARGINS(0, shadowSize, 0, shadowSize);
+ int result = DwmExtendFrameIntoClientArea(hWnd, ref margins);
+ return result == 0;
+ }
+
+ public static bool WindowSheetOfGlass(IntPtr hWnd)
+ {
+ MARGINS margins = new MARGINS();
+
+ //Margins set to All:-1 - Sheet Of Glass effect
+ margins.SheetOfGlass();
+ int result = DwmExtendFrameIntoClientArea(hWnd, ref margins);
+ return result == 0;
+ }
+
+ public static bool WindowDisableRendering(IntPtr hWnd)
+ {
+ int ncrp = (int)DWMNCRENDERINGPOLICY.Disabled;
+ // Disable non-client area rendering on the window.
+ int result = DwmSetWindowAttribute(hWnd, DWMWINDOWATTRIBUTE.NCRenderingPolicy, ref ncrp, sizeof(int));
+ return result == 0;
+ }
+}
\ No newline at end of file
diff --git a/ObservatoryCore/UI/NotificationForm.Designer.cs b/ObservatoryCore/UI/NotificationForm.Designer.cs
index 012e455..444350b 100644
--- a/ObservatoryCore/UI/NotificationForm.Designer.cs
+++ b/ObservatoryCore/UI/NotificationForm.Designer.cs
@@ -28,12 +28,60 @@
///
private void InitializeComponent()
{
- this.components = new System.ComponentModel.Container();
+ this.Title = new System.Windows.Forms.Label();
+ this.Body = new System.Windows.Forms.Label();
+ this.SuspendLayout();
+ //
+ // Title
+ //
+ this.Title.Font = new System.Drawing.Font("Segoe UI", 24F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
+ this.Title.ForeColor = System.Drawing.Color.OrangeRed;
+ this.Title.Location = new System.Drawing.Point(5, 5);
+ this.Title.MaximumSize = new System.Drawing.Size(355, 0);
+ this.Title.Name = "Title";
+ this.Title.Size = new System.Drawing.Size(338, 45);
+ this.Title.TabIndex = 0;
+ this.Title.Text = "Title";
+ //
+ // Body
+ //
+ this.Body.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
+ | System.Windows.Forms.AnchorStyles.Right)));
+ this.Body.AutoSize = true;
+ this.Body.Font = new System.Drawing.Font("Segoe UI", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
+ this.Body.ForeColor = System.Drawing.Color.OrangeRed;
+ this.Body.Location = new System.Drawing.Point(12, 45);
+ this.Body.MaximumSize = new System.Drawing.Size(320, 85);
+ this.Body.Name = "Body";
+ this.Body.Size = new System.Drawing.Size(51, 31);
+ this.Body.TabIndex = 1;
+ this.Body.Text = "Body";
+ this.Body.UseCompatibleTextRendering = true;
+ //
+ // NotificationForm
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
- this.ClientSize = new System.Drawing.Size(800, 450);
+ this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
+ this.ClientSize = new System.Drawing.Size(355, 145);
+ this.ControlBox = false;
+ this.Controls.Add(this.Body);
+ this.Controls.Add(this.Title);
+ this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
+ this.MaximizeBox = false;
+ this.MinimizeBox = false;
+ this.Name = "NotificationForm";
+ this.ShowIcon = false;
+ this.ShowInTaskbar = false;
this.Text = "NotificationForm";
+ this.ResumeLayout(false);
+ this.PerformLayout();
+
}
#endregion
+
+ private Label Title;
+ private Label Body;
}
}
\ No newline at end of file
diff --git a/ObservatoryCore/UI/NotificationForm.cs b/ObservatoryCore/UI/NotificationForm.cs
index 26c5ef2..dee4e2e 100644
--- a/ObservatoryCore/UI/NotificationForm.cs
+++ b/ObservatoryCore/UI/NotificationForm.cs
@@ -1,9 +1,12 @@
-using System;
+using Observatory.Framework;
+using Observatory.Framework.Files.Journal;
+using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
+using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
@@ -12,11 +15,206 @@ namespace Observatory.UI
{
public partial class NotificationForm : Form
{
- public NotificationForm()
+ private Color _color;
+ private readonly Guid _guid;
+ private readonly System.Timers.Timer _timer;
+ private bool _defaultPosition = true;
+ private Point _originalLocation;
+
+ protected override bool ShowWithoutActivation => true;
+ protected override CreateParams CreateParams
+ {
+ get
+ {
+ CreateParams cp = base.CreateParams;
+ cp.ExStyle |= 0x00000008; // WS_EX_TOPMOST
+ return cp;
+ }
+ }
+
+ public NotificationForm(Guid guid, NotificationArgs args)
{
+ _guid = guid;
+ _color = Color.FromArgb((int)Properties.Core.Default.NativeNotifyColour);
InitializeComponent();
+
+ Title.Paint += DrawText;
+ Body.Paint += DrawText;
+
+ if (System.Environment.OSVersion.Version.Major >= 6 && DwmHelper.IsCompositionEnabled())
+ {
+ if (Environment.OSVersion.Version.Major > 6)
+ {
+ DwmHelper.Windows10EnableBlurBehind(Handle);
+ }
+ else
+ {
+ DwmHelper.WindowEnableBlurBehind(Handle);
+ }
+
+ // For some reason this causes the window to become all white on my own
+ // PC. Looks very similar to strange system-specific all-white behaviour
+ // of Avalonia.
+ // DwmHelper.WindowBorderlessDropShadow(Handle, 2);
+ }
+
+
+ Title.ForeColor = _color;
+ Title.Text = args.Title;
+ Title.Font = new Font(Properties.Core.Default.NativeNotifyFont, 24);
+ Body.ForeColor = _color;
+ Body.Text = args.Detail;
+ Body.Font = new Font(Properties.Core.Default.NativeNotifyFont, 14);
+ this.Paint += DrawBorder;
+
+ AdjustPosition(args.XPos / 100, args.YPos / 100);
+
+ _timer = new();
+ _timer.Elapsed += CloseNotification;
+ if (args.Timeout != 0)
+ {
+ _timer.Interval = args.Timeout == -1 ? Properties.Core.Default.NativeNotifyTimeout : args.Timeout;
+ _timer.Start();
+ }
}
- public Guid Guid;
+ public void Update(NotificationArgs notificationArgs)
+ {
+ Title.Text = notificationArgs.Title;
+ Body.Text = notificationArgs.Detail;
+ }
+
+ private void AdjustPosition(double x = -1.0, double y = -1.0)
+ {
+ int screen = Properties.Core.Default.NativeNotifyScreen;
+ int corner = Properties.Core.Default.NativeNotifyCorner;
+ Rectangle screenBounds;
+
+
+ if (screen == -1 || screen > Screen.AllScreens.Length)
+ if (Screen.AllScreens.Length == 1)
+ screenBounds = Screen.GetBounds(this);
+ else
+ screenBounds = Screen.PrimaryScreen.Bounds;
+ else
+ screenBounds = Screen.AllScreens[screen - 1].Bounds;
+
+ if (x >= 0 && y >= 0)
+ {
+ _defaultPosition = false;
+ int xLocation = Convert.ToInt32(screenBounds.Width * x);
+ int yLocation = Convert.ToInt32(screenBounds.Height * x);
+ Location = Point.Add(screenBounds.Location, new Size(xLocation, yLocation));
+ }
+ else
+ {
+ _defaultPosition = true;
+ switch (corner)
+ {
+ default:
+ case 0:
+ Location = Point.Add(
+ new Point(screenBounds.Right, screenBounds.Bottom),
+ new Size(-(Width+50), -(Height+50)));
+ break;
+ case 1:
+ Location = Point.Add(
+ new Point(screenBounds.Left, screenBounds.Bottom),
+ new Size(50, -(Height + 50)));
+ break;
+ case 2:
+ Location = Point.Add(
+ new Point(screenBounds.Right, screenBounds.Top),
+ new Size(-(Width + 50), 50));
+ break;
+ case 3:
+ Location = Point.Add(
+ new Point(screenBounds.Left, screenBounds.Top),
+ new Size(50, 00));
+ break;
+ }
+ _originalLocation = new Point(Location.X, Location.Y);
+ }
+ }
+
+ private void DrawBorder(object? sender, PaintEventArgs e)
+ {
+ using (Pen pen = new Pen(_color))
+ {
+ pen.Width = 6;
+ e.Graphics.DrawLine(pen, 0, 0, Width, 0);
+ e.Graphics.DrawLine(pen, 0, 0, 0, Height);
+ e.Graphics.DrawLine(pen, 0, Height, Width, Height);
+ e.Graphics.DrawLine(pen, Width, 0, Width, Height);
+ }
+ }
+
+ protected override void WndProc(ref Message m)
+ {
+
+ switch (m.Msg)
+ {
+ case DwmHelper.WM_DWMCOMPOSITIONCHANGED:
+ if (System.Environment.OSVersion.Version.Major >= 6 && DwmHelper.IsCompositionEnabled())
+ {
+ var policy = DwmHelper.DWMNCRENDERINGPOLICY.Enabled;
+ DwmHelper.WindowSetAttribute(Handle, DwmHelper.DWMWINDOWATTRIBUTE.NCRenderingPolicy, (int)policy);
+ DwmHelper.WindowBorderlessDropShadow(Handle, 2);
+ m.Result = IntPtr.Zero;
+ }
+ break;
+ case 0x0084:
+ m.Result = (IntPtr)(-1);
+ return;
+ default:
+ break;
+ }
+ base.WndProc(ref m);
+ }
+
+ private void DrawText(object? sender, PaintEventArgs e)
+ {
+ if (sender != null)
+ {
+ var label = (Label)sender;
+ e.Graphics.Clear(Color.Transparent);
+ using (var sf = new StringFormat())
+ using (var brush = new SolidBrush(label.ForeColor))
+ {
+ sf.Alignment = sf.LineAlignment = StringAlignment.Near;
+ e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
+ e.Graphics.DrawString(label.Text, label.Font, brush, label.ClientRectangle, sf);
+ }
+ }
+ }
+
+ public Guid Guid { get => _guid; }
+
+ private void AdjustText()
+ {
+
+ }
+
+ private void CloseNotification(object? sender, System.Timers.ElapsedEventArgs e)
+ {
+ try
+ {
+ Close();
+ }
+ catch
+ {
+ try
+ {
+ this.Invoke(() => Close());
+ }
+ catch
+ {
+ throw new Exception("blah");
+ }
+ }
+
+ _timer.Stop();
+ _timer.Dispose();
+ }
}
}
diff --git a/ObservatoryCore/UI/NotificationForm.resx b/ObservatoryCore/UI/NotificationForm.resx
index 1af7de1..f298a7b 100644
--- a/ObservatoryCore/UI/NotificationForm.resx
+++ b/ObservatoryCore/UI/NotificationForm.resx
@@ -1,64 +1,4 @@
-
-
-
+
diff --git a/ObservatoryCore/UI/PluginHelper.cs b/ObservatoryCore/UI/PluginHelper.cs
index ceee265..cb0fc56 100644
--- a/ObservatoryCore/UI/PluginHelper.cs
+++ b/ObservatoryCore/UI/PluginHelper.cs
@@ -1,11 +1,7 @@
using Observatory.Framework.Interfaces;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Security.Cryptography.X509Certificates;
-using System.Speech.Synthesis;
-using System.Text;
-using System.Threading.Tasks;
+using System.Collections;
+using Observatory.PluginManagement;
+using Observatory.Utils;
namespace Observatory.UI
{
@@ -47,12 +43,20 @@ namespace Observatory.UI
if (plugin.PluginUI.PluginUIType == Framework.PluginUI.UIType.Basic)
uiPanels.Add(newItem, CreateBasicUI(plugin));
+ else if (plugin.PluginUI.PluginUIType == Framework.PluginUI.UIType.Panel)
+ uiPanels.Add(newItem, (Panel)plugin.PluginUI.UI);
}
private static Panel CreateBasicUI(IObservatoryPlugin plugin)
{
Panel panel = new();
- var columnSorter = new DefaultSorter();
+
+ IObservatoryComparer columnSorter;
+ if (plugin.ColumnSorter != null)
+ columnSorter = plugin.ColumnSorter;
+ else
+ columnSorter = new DefaultSorter();
+
ListView listView = new()
{
View = View.Details,
@@ -62,7 +66,8 @@ namespace Observatory.UI
BackColor = Color.FromArgb(64, 64, 64),
ForeColor = Color.LightGray,
GridLines = true,
- ListViewItemSorter = columnSorter
+ ListViewItemSorter = columnSorter,
+ Font = new Font(new FontFamily("Segoe UI"), 10, FontStyle.Regular)
};
foreach (var property in plugin.PluginUI.DataGrid.First().GetType().GetProperties())
@@ -75,20 +80,20 @@ namespace Observatory.UI
if (e.Column == columnSorter.SortColumn)
{
// Reverse the current sort direction for this column.
- if (columnSorter.Order == SortOrder.Ascending)
+ if (columnSorter.Order == 1)
{
- columnSorter.Order = SortOrder.Descending;
+ columnSorter.Order = -1;
}
else
{
- columnSorter.Order = SortOrder.Ascending;
+ columnSorter.Order = 1;
}
}
else
{
// Set the column number that is to be sorted; default to ascending.
columnSorter.SortColumn = e.Column;
- columnSorter.Order = SortOrder.Ascending;
+ columnSorter.Order = 1;
}
listView.Sort();
};
@@ -97,20 +102,58 @@ namespace Observatory.UI
plugin.PluginUI.DataGrid.CollectionChanged += (sender, e) =>
{
- if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add &&
- e.NewItems != null)
+ listView.Invoke(() =>
{
- foreach (var newItem in e.NewItems)
+ if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add &&
+ e.NewItems != null)
{
- ListViewItem newListItem = new();
- foreach (var property in newItem.GetType().GetProperties())
+ foreach (var newItem in e.NewItems)
{
- newListItem.SubItems.Add(property.GetValue(newItem)?.ToString());
+ ListViewItem newListItem = new();
+ foreach (var property in newItem.GetType().GetProperties())
+ {
+ newListItem.SubItems.Add(property.GetValue(newItem)?.ToString());
+ }
+ newListItem.SubItems.RemoveAt(0);
+ listView.Items.Add(newListItem);
}
- newListItem.SubItems.RemoveAt(0);
- listView.Items.Add(newListItem);
}
- }
+
+ if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove &&
+ e.OldItems != null)
+ {
+ foreach (var oldItem in e.OldItems)
+ {
+ ListViewItem oldListItem = new();
+ foreach (var property in oldItem.GetType().GetProperties())
+ {
+ oldListItem.SubItems.Add(property.GetValue(oldItem)?.ToString());
+ }
+ oldListItem.SubItems.RemoveAt(0);
+
+ var itemToRemove = listView.Items.Cast().Where(i => i.SubItems.Cast().SequenceEqual(oldListItem.SubItems.Cast())).First();
+ if (itemToRemove != null)
+ {
+ listView.Items.Remove(itemToRemove);
+ }
+ }
+ }
+
+ if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Reset)
+ {
+ listView.Items.Clear();
+ foreach (var item in plugin.PluginUI.DataGrid)
+ {
+ ListViewItem listItem = new();
+ foreach (var property in item.GetType().GetProperties())
+ {
+ listItem.SubItems.Add(property.GetValue(item)?.ToString());
+ }
+ listItem.SubItems.RemoveAt(0);
+ listView.Items.Add(listItem);
+ }
+ }
+ });
};
return panel;
diff --git a/ObservatoryCore/UI/SettingsPanel.cs b/ObservatoryCore/UI/SettingsPanel.cs
index 36bc925..3c9da92 100644
--- a/ObservatoryCore/UI/SettingsPanel.cs
+++ b/ObservatoryCore/UI/SettingsPanel.cs
@@ -2,10 +2,12 @@
using Observatory.Framework.Interfaces;
using System;
using System.Collections.Generic;
+using System.Data.Common;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
+using System.Windows.Forms.VisualStyles;
namespace Observatory.UI
{
@@ -22,7 +24,7 @@ namespace Observatory.UI
_adjustPanelsBelow = adjustPanelsBelow;
// Filtered to only settings without SettingIgnore attribute
- var settings = PluginManagement.PluginManager.GetSettingDisplayNames(plugin).Where(s => !Attribute.IsDefined(s.Key, typeof (SettingIgnore)));
+ var settings = PluginManagement.PluginManager.GetSettingDisplayNames(plugin.Settings).Where(s => !Attribute.IsDefined(s.Key, typeof (SettingIgnore)));
CreateControls(settings);
}
@@ -30,35 +32,300 @@ namespace Observatory.UI
private void CreateControls(IEnumerable> settings)
{
int controlRow = 0;
- bool nextColumn = true;
+ bool recentHalfCol = false;
- // Handle bool (checkbox) settings first and keep them grouped together
- foreach (var setting in settings.Where(s => s.Key.PropertyType == typeof(bool)))
+ foreach (var setting in settings)
{
- CheckBox checkBox = new()
+ // Reset the column tracking for checkboxes if this isn't a checkbox
+ if (setting.Key.PropertyType.Name != "Boolean" && setting.Key.PropertyType.Name != "Button")
+ recentHalfCol = false;
+
+ switch (setting.Key.GetValue(_plugin.Settings))
{
- Text = setting.Value,
- Checked = (bool?)setting.Key.GetValue(_plugin.Settings) ?? false
- };
+ case bool:
+ var checkBox = CreateBoolSetting(setting);
+ controlRow += recentHalfCol ? 0 : 1;
+ checkBox.Location = GetSettingPosition(controlRow, recentHalfCol);
- checkBox.CheckedChanged += (object? _, EventArgs _) =>
- {
- setting.Key.SetValue(_plugin.Settings, checkBox.Checked);
- PluginManagement.PluginManager.GetInstance.SaveSettings(_plugin, _plugin.Settings);
- };
+ recentHalfCol = !recentHalfCol;
- checkBox.Location = new Point(nextColumn ? 10 : 130, 3 + controlRow * 29);
- controlRow += nextColumn ? 0 : 1;
- nextColumn = !nextColumn;
+ Controls.Add(checkBox);
+ break;
+ case string:
+ var stringLabel = CreateSettingLabel(setting.Value);
+ var textBox = CreateStringSetting(setting.Key);
+ controlRow++;
+ stringLabel.Location = GetSettingPosition(controlRow);
+ textBox.Location = GetSettingPosition(controlRow, true);
+
+ Controls.Add(stringLabel);
+ Controls.Add(textBox);
- Controls.Add(checkBox);
+ break;
+ case FileInfo:
+ var fileLabel = CreateSettingLabel(setting.Value);
+ var pathTextBox = CreateFilePathSetting(setting.Key);
+ var pathButton = CreateFileBrowseSetting(setting.Key, pathTextBox);
+
+ controlRow++;
+
+ fileLabel.Location = GetSettingPosition(controlRow);
+ pathTextBox.Location = GetSettingPosition(controlRow, true);
+ pathButton.Location = GetSettingPosition(++controlRow, true);
+
+ Controls.Add(fileLabel);
+ Controls.Add(pathTextBox);
+ Controls.Add(pathButton);
+
+ break;
+ case int:
+ // We have two options for integer values:
+ // 1) A slider (explicit by way of the SettingIntegerUseSlider attribute and bounded to 0..100 by default)
+ // 2) A numeric up/down (default otherwise, and is unbounded by default).
+ // Bounds for both can be set via the SettingNumericBounds attribute, only the up/down uses Increment.
+ var intLabel = CreateSettingLabel(setting.Value);
+ Control intControl;
+ controlRow++;
+ if (System.Attribute.IsDefined(setting.Key, typeof(SettingNumericUseSlider)))
+ {
+ intControl = CreateSettingTrackbar(setting.Key);
+ }
+ else
+ {
+ intControl = CreateSettingNumericUpDown(setting.Key);
+ }
+ intLabel.Location = GetSettingPosition(controlRow);
+ intControl.Location = GetSettingPosition(controlRow, true);
+
+ Controls.Add(intLabel);
+ Controls.Add(intControl);
+ break;
+ case Action action:
+ var button = CreateSettingButton(setting.Value, action);
+
+ controlRow += recentHalfCol ? 0 : 1;
+ button.Location = GetSettingPosition(controlRow, recentHalfCol);
+ recentHalfCol = !recentHalfCol;
+
+ Controls.Add(button);
+ break;
+ case Dictionary dictSetting:
+ var dictLabel = CreateSettingLabel(setting.Value);
+ var dropdown = CreateSettingDropdown(setting.Key, dictSetting);
+ controlRow++;
+
+ dictLabel.Location = GetSettingPosition(controlRow);
+ dropdown.Location = GetSettingPosition(controlRow, true);
+ Controls.Add(dictLabel);
+ Controls.Add(dropdown);
+ break;
+ default:
+ break;
+ }
+ }
+ Height = 3 + controlRow * 29;
+ }
+
+ private static Point GetSettingPosition(int rowNum, bool secondCol = false)
+ {
+ return new Point(10 + (secondCol ? 200 : 0), -26 + rowNum * 29);
+ }
+
+
+ private Label CreateSettingLabel(string settingName)
+ {
+ Label label = new()
+ {
+ Text = settingName + ": ",
+ TextAlign = System.Drawing.ContentAlignment.MiddleRight,
+ Width = 200,
+ ForeColor = Color.LightGray
+ };
+
+ return label;
+ }
+
+ private ComboBox CreateSettingDropdown(PropertyInfo setting, Dictionary dropdownItems)
+ {
+ var backingValueName = (SettingBackingValue?)Attribute.GetCustomAttribute(setting, typeof(SettingBackingValue));
+
+ var backingValue = from s in PluginManagement.PluginManager.GetSettingDisplayNames(_plugin.Settings)
+ where s.Value == backingValueName?.BackingProperty
+ select s.Key;
+
+ if (backingValue.Count() != 1)
+ throw new($"{_plugin.ShortName}: Dictionary settings must have exactly one backing value.");
+
+ ComboBox comboBox = new()
+ {
+ Width = 200,
+ DropDownStyle = ComboBoxStyle.DropDownList
+ };
+
+ comboBox.Items.AddRange(dropdownItems.OrderBy(s => s.Key).Select(s => s.Key).ToArray());
+
+ string? currentSelection = backingValue.First().GetValue(_plugin.Settings)?.ToString();
+
+ if (currentSelection?.Length > 0)
+ {
+ comboBox.SelectedItem = currentSelection;
}
- // Then the rest
- foreach (var setting in settings.Where(s => s.Key.PropertyType != typeof(bool)))
+ comboBox.SelectedValueChanged += (sender, e) =>
+ {
+ backingValue.First().SetValue(_plugin.Settings, comboBox.SelectedItem.ToString());
+ SaveSettings();
+ };
+
+ return comboBox;
+ }
+
+ private Button CreateSettingButton(string settingName, Action action)
+ {
+ Button button = new()
+ {
+ Text = settingName
+ };
+
+ button.Click += (sender, e) =>
+ {
+ action.Invoke();
+ SaveSettings();
+ };
+
+ return button;
+ }
+
+ private TrackBar CreateSettingTrackbar(PropertyInfo setting)
+ {
+ SettingNumericBounds? bounds = (SettingNumericBounds?)System.Attribute.GetCustomAttribute(setting, typeof(SettingNumericBounds));
+ TrackBar trackBar = new ()
+ {
+ Orientation = Orientation.Horizontal,
+ TickStyle = TickStyle.Both,
+ Width = 200,
+ Minimum = Convert.ToInt32(bounds?.Minimum ?? 0),
+ Maximum = Convert.ToInt32(bounds?.Maximum ?? 100)
+ };
+
+ trackBar.Value = (int?)setting.GetValue(_plugin.Settings) ?? 0;
+
+ trackBar.ValueChanged += (sender, e) =>
+ {
+ setting.SetValue(_plugin.Settings, trackBar.Value);
+ SaveSettings();
+ };
+
+ return trackBar;
+ }
+
+ private NumericUpDown CreateSettingNumericUpDown(PropertyInfo setting)
+ {
+ SettingNumericBounds? bounds = (SettingNumericBounds?)System.Attribute.GetCustomAttribute(setting, typeof(SettingNumericBounds));
+ NumericUpDown numericUpDown = new()
{
- }
+ Width = 200,
+ Minimum = Convert.ToInt32(bounds?.Minimum ?? Int32.MinValue),
+ Maximum = Convert.ToInt32(bounds?.Maximum ?? Int32.MaxValue),
+ Increment = Convert.ToInt32(bounds?.Increment ?? 1)
+ };
+
+ numericUpDown.Value = (int?)setting.GetValue(_plugin.Settings) ?? 0;
+
+ numericUpDown.ValueChanged += (sender, e) =>
+ {
+ setting.SetValue(_plugin.Settings, numericUpDown.Value);
+ SaveSettings();
+ };
+
+ return numericUpDown;
+ }
+
+ private CheckBox CreateBoolSetting(KeyValuePair setting)
+ {
+ CheckBox checkBox = new()
+ {
+ Text = setting.Value,
+ TextAlign= System.Drawing.ContentAlignment.MiddleLeft,
+ Checked = (bool?)setting.Key.GetValue(_plugin.Settings) ?? false,
+ Width = 200,
+ ForeColor = Color.LightGray
+ };
+
+ checkBox.CheckedChanged += (sender, e) =>
+ {
+ setting.Key.SetValue(_plugin.Settings, checkBox.Checked);
+ SaveSettings();
+ };
+
+ return checkBox;
+ }
+
+ private TextBox CreateStringSetting(PropertyInfo setting)
+ {
+ TextBox textBox = new()
+ {
+ Text = (setting.GetValue(_plugin.Settings) ?? String.Empty).ToString(),
+ Width = 200
+ };
+
+ textBox.TextChanged += (object? sender, EventArgs e) =>
+ {
+ setting.SetValue(_plugin.Settings, textBox.Text);
+ SaveSettings();
+ };
+
+ return textBox;
+ }
+
+ private TextBox CreateFilePathSetting(PropertyInfo setting)
+ {
+ var fileInfo = (FileInfo?)setting.GetValue(_plugin.Settings);
+
+ TextBox textBox = new()
+ {
+ Text = fileInfo?.FullName ?? string.Empty,
+ Width = 200
+ };
+
+ textBox.TextChanged += (object? sender, EventArgs e) =>
+ {
+ setting.SetValue(_plugin.Settings, new FileInfo(textBox.Text));
+ SaveSettings();
+ };
+
+ return textBox;
+ }
+
+ private Button CreateFileBrowseSetting(PropertyInfo setting, TextBox textBox)
+ {
+ Button button = new()
+ {
+ Text = "Browse"
+ };
+
+ button.Click += (object? sender, EventArgs e) =>
+ {
+ var currentDir = ((FileInfo?)setting.GetValue(_plugin.Settings))?.DirectoryName;
+
+ OpenFileDialog ofd = new OpenFileDialog()
+ {
+ Title = "Select File...",
+ Filter = "Lua files (*.lua)|*.lua|All files (*.*)|*.*",
+ FilterIndex = 0,
+ InitialDirectory = currentDir ?? Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
+ };
+
+ var browseResult = ofd.ShowDialog();
+
+ if (browseResult == DialogResult.OK)
+ {
+ textBox.Text = ofd.FileName;
+ }
+ };
+
+ return button;
}
private Label CreateHeader(string pluginName)
@@ -92,5 +359,10 @@ namespace Observatory.UI
}
this.Parent?.ResumeLayout();
}
+
+ private void SaveSettings()
+ {
+ PluginManagement.PluginManager.GetInstance.SaveSettings(_plugin, _plugin.Settings);
+ }
}
}
diff --git a/ObservatoryCore/Utils/LogMonitor.cs b/ObservatoryCore/Utils/LogMonitor.cs
index 2fe2383..45fb68f 100644
--- a/ObservatoryCore/Utils/LogMonitor.cs
+++ b/ObservatoryCore/Utils/LogMonitor.cs
@@ -284,7 +284,7 @@ namespace Observatory.Utils
if (Properties.Core.Default.JournalFolder != path)
{
Properties.Core.Default.JournalFolder = path;
- Properties.Core.Default.Save();
+ SettingsManager.Save();
}
return logDirectory;
diff --git a/ObservatoryCore/Utils/SettingsManager.cs b/ObservatoryCore/Utils/SettingsManager.cs
new file mode 100644
index 0000000..c1d8410
--- /dev/null
+++ b/ObservatoryCore/Utils/SettingsManager.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+
+namespace Observatory.Utils
+{
+ internal static class SettingsManager
+ {
+ internal static void Save()
+ {
+#if DEBUG || RELEASE
+ Properties.Core.Default.Save();
+#elif PORTABLE
+
+ Dictionary settings = new();
+
+ foreach (PropertyInfo property in Properties.Core.Default.GetType().GetProperties())
+ {
+ if (property.CanRead && property.CanWrite && !property.GetIndexParameters().Any())
+ settings.Add(
+ property.Name,
+ property.GetValue(Properties.Core.Default)
+ );
+ }
+
+ string serializedSettings = JsonSerializer.Serialize(settings, new JsonSerializerOptions()
+ {
+ ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.Preserve,
+
+ });
+ File.WriteAllText("Observatory.config", serializedSettings);
+#endif
+ }
+
+ internal static void Load()
+ {
+#if PORTABLE
+ if (File.Exists("Observatory.config"))
+ {
+ string savedSettings = File.ReadAllText("Observatory.config");
+ Dictionary? settings = JsonSerializer.Deserialize>(savedSettings);
+ if (settings != null)
+ {
+ var properties = Properties.Core.Default.GetType().GetProperties();
+
+ foreach (var savedProperty in settings)
+ {
+
+ var currentProperty = properties.Where(p => p.Name == savedProperty.Key);
+ if (currentProperty.Any())
+ {
+ JsonElement? value = (JsonElement?)savedProperty.Value;
+ var deserializedValue = value?.Deserialize(currentProperty.First().PropertyType);
+ currentProperty.First().SetValue(Properties.Core.Default, deserializedValue);
+ }
+
+ }
+ }
+ }
+#endif
+ }
+ }
+}
diff --git a/ObservatoryDev/ObservatoryDev.sln b/ObservatoryDev/ObservatoryDev.sln
index a9a446e..151a08b 100644
--- a/ObservatoryDev/ObservatoryDev.sln
+++ b/ObservatoryDev/ObservatoryDev.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.30128.74
+# Visual Studio Version 17
+VisualStudioVersion = 17.3.32922.545
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ObservatoryBotanist", "..\ObservatoryBotanist\ObservatoryBotanist.csproj", "{498F7360-D443-4D64-895C-9EAB5570D019}"
EndProject
@@ -16,27 +16,38 @@ EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Portable|Any CPU = Portable|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{498F7360-D443-4D64-895C-9EAB5570D019}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{498F7360-D443-4D64-895C-9EAB5570D019}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {498F7360-D443-4D64-895C-9EAB5570D019}.Portable|Any CPU.ActiveCfg = Portable|Any CPU
+ {498F7360-D443-4D64-895C-9EAB5570D019}.Portable|Any CPU.Build.0 = Portable|Any CPU
{498F7360-D443-4D64-895C-9EAB5570D019}.Release|Any CPU.ActiveCfg = Release|Any CPU
{498F7360-D443-4D64-895C-9EAB5570D019}.Release|Any CPU.Build.0 = Release|Any CPU
{0E1C4F16-858E-4E53-948A-77D81A8F3395}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0E1C4F16-858E-4E53-948A-77D81A8F3395}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0E1C4F16-858E-4E53-948A-77D81A8F3395}.Portable|Any CPU.ActiveCfg = Portable|Any CPU
+ {0E1C4F16-858E-4E53-948A-77D81A8F3395}.Portable|Any CPU.Build.0 = Portable|Any CPU
{0E1C4F16-858E-4E53-948A-77D81A8F3395}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0E1C4F16-858E-4E53-948A-77D81A8F3395}.Release|Any CPU.Build.0 = Release|Any CPU
{E0FCF2A2-BF56-4F4D-836B-92A0E8269192}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E0FCF2A2-BF56-4F4D-836B-92A0E8269192}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E0FCF2A2-BF56-4F4D-836B-92A0E8269192}.Portable|Any CPU.ActiveCfg = Portable|Any CPU
+ {E0FCF2A2-BF56-4F4D-836B-92A0E8269192}.Portable|Any CPU.Build.0 = Portable|Any CPU
{E0FCF2A2-BF56-4F4D-836B-92A0E8269192}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E0FCF2A2-BF56-4F4D-836B-92A0E8269192}.Release|Any CPU.Build.0 = Release|Any CPU
{27ABA3B7-AB3C-465F-BA40-4F06BD803811}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{27ABA3B7-AB3C-465F-BA40-4F06BD803811}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {27ABA3B7-AB3C-465F-BA40-4F06BD803811}.Portable|Any CPU.ActiveCfg = Portable|Any CPU
+ {27ABA3B7-AB3C-465F-BA40-4F06BD803811}.Portable|Any CPU.Build.0 = Portable|Any CPU
{27ABA3B7-AB3C-465F-BA40-4F06BD803811}.Release|Any CPU.ActiveCfg = Release|Any CPU
{27ABA3B7-AB3C-465F-BA40-4F06BD803811}.Release|Any CPU.Build.0 = Release|Any CPU
{BC57225F-D89B-4853-A816-9AB4865E7AC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BC57225F-D89B-4853-A816-9AB4865E7AC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BC57225F-D89B-4853-A816-9AB4865E7AC5}.Portable|Any CPU.ActiveCfg = Portable|Any CPU
+ {BC57225F-D89B-4853-A816-9AB4865E7AC5}.Portable|Any CPU.Build.0 = Portable|Any CPU
{BC57225F-D89B-4853-A816-9AB4865E7AC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BC57225F-D89B-4853-A816-9AB4865E7AC5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
diff --git a/ObservatoryExplorer/DefaultCriteria.cs b/ObservatoryExplorer/DefaultCriteria.cs
index 3afe5f2..1d23ca5 100644
--- a/ObservatoryExplorer/DefaultCriteria.cs
+++ b/ObservatoryExplorer/DefaultCriteria.cs
@@ -16,6 +16,10 @@ namespace Observatory.Explorer
bool isRing = scan.BodyName.Contains("Ring");
+#if DEBUG
+ // results.Add("Test Scan Event", "Test Detail");
+#endif
+
#region Landable Checks
if (scan.Landable)
{
diff --git a/ObservatoryExplorer/ObservatoryExplorer.csproj b/ObservatoryExplorer/ObservatoryExplorer.csproj
index 4b0b304..0dac63d 100644
--- a/ObservatoryExplorer/ObservatoryExplorer.csproj
+++ b/ObservatoryExplorer/ObservatoryExplorer.csproj
@@ -5,6 +5,7 @@
enable
false
ObservatoryKey.snk
+ Debug;Release;Portable
@@ -20,10 +21,10 @@
-
-
-
-
+
+
+
+
diff --git a/ObservatoryFramework/Interfaces.cs b/ObservatoryFramework/Interfaces.cs
index 3f7e0e3..27ee27b 100644
--- a/ObservatoryFramework/Interfaces.cs
+++ b/ObservatoryFramework/Interfaces.cs
@@ -1,7 +1,6 @@
-using System;
-using System.Net.Http;
-using Observatory.Framework.Files;
+using Observatory.Framework.Files;
using Observatory.Framework.Files.Journal;
+using System.Xml.XPath;
namespace Observatory.Framework.Interfaces
{
@@ -50,6 +49,13 @@ namespace Observatory.Framework.Interfaces
///
public object Settings { get; set; }
+ ///
+ /// Plugin-specific object implementing the IComparer interface which is used to sort columns in the basic UI datagrid.
+ /// If omitted a basic numeric compare sorter is used.
+ ///
+ public IObservatoryComparer ColumnSorter
+ { get => null; }
+
}
///
@@ -163,13 +169,6 @@ namespace Observatory.Framework.Interfaces
/// Grid items to be added. Object types should match original template item used to create the grid.
public void AddGridItems(IObservatoryWorker worker, IEnumerable items);
- ///
- /// Add multiple items to the bottom of the basic UI grid.
- ///
- /// Reference to the calling plugin's worker interface.
- /// Grid items to be added. Object types should match original template item used to create the grid.
- public void AddGridItems(IObservatoryWorker worker, IEnumerable items);
-
///
/// Clears basic UI grid, removing all items.
///
@@ -221,4 +220,21 @@ namespace Observatory.Framework.Interfaces
///
public string PluginStorageFolder { get; }
}
+
+ ///
+ /// Extends the base IComparer interface with exposed values for the column ID and sort order to use.
+ ///
+ public interface IObservatoryComparer : System.Collections.IComparer
+ {
+ ///
+ /// Column ID to be currently sorted by.
+ ///
+ public int SortColumn { get; set; }
+
+ ///
+ /// Current order of sorting. Ascending = 1, Descending = -1, No sorting = 0.
+ ///
+ public int Order { get; set; }
+ }
+
}
diff --git a/ObservatoryFramework/ObservatoryFramework.csproj b/ObservatoryFramework/ObservatoryFramework.csproj
index 8da2df4..c07018a 100644
--- a/ObservatoryFramework/ObservatoryFramework.csproj
+++ b/ObservatoryFramework/ObservatoryFramework.csproj
@@ -4,6 +4,7 @@
net6.0
enable
Observatory.Framework
+ Debug;Release;Portable
@@ -17,5 +18,9 @@
ObservatoryFramework.xml
+
+
+ ObservatoryFramework.xml
+
diff --git a/ObservatoryFramework/ObservatoryFramework.xml b/ObservatoryFramework/ObservatoryFramework.xml
index a046077..2b3a07a 100644
--- a/ObservatoryFramework/ObservatoryFramework.xml
+++ b/ObservatoryFramework/ObservatoryFramework.xml
@@ -1350,6 +1350,12 @@
If a public property is necessary but not required to be user accessible the [SettingIgnore] property will suppress display.
+
+
+ Plugin-specific object implementing the IComparer interface which is used to sort columns in the basic UI datagrid.
+ If omitted a basic string compare sorter is used.
+
+
Interface for worker plugins which process journal data to update their UI or send notifications.
@@ -1504,6 +1510,21 @@
Retrieves and ensures creation of a location which can be used by the plugin to store persistent data.
+
+
+ Extends the base IComparer interface with exposed values for the column ID and sort order to use.
+
+
+
+
+ Column ID to be currently sorted by.
+
+
+
+
+ Current order of sorting. Ascending = 1, Descending = -1, No sorting = 0.
+
+
Class permitting plugins to provide their UI, if any, to Observatory Core.
@@ -1516,13 +1537,12 @@
- UI object used by plugins with UIType.Avalonia.
- (Untested/not implemented)
+ UI object used by plugins with UIType.Panel.
- Collection bound to DataGrid used byu plugins with UIType.Basic.
+ Collection bound to DataGrid used by plugins with UIType.Basic.
Objects in collection should be of a class defined within the plugin consisting of string properties. Each object is a single row, and the property names are used as column headers.
@@ -1531,7 +1551,7 @@
Instantiate PluginUI of UIType.Basic.
- Collection bound to DataGrid used byu plugins with UIType.Basic.
+ Collection bound to DataGrid used by plugins with UIType.Basic.
Objects in collection should be of a class defined within the plugin consisting of string properties. Each object is a single row, and the property names are used as column headers.
diff --git a/ObservatoryFramework/PluginUI.cs b/ObservatoryFramework/PluginUI.cs
index 0e73312..c65edbc 100644
--- a/ObservatoryFramework/PluginUI.cs
+++ b/ObservatoryFramework/PluginUI.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
+using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
@@ -17,13 +18,12 @@ namespace Observatory.Framework
public readonly UIType PluginUIType;
///
- /// UI object used by plugins with UIType.Avalonia.
- /// (Untested/not implemented)
+ /// UI object used by plugins with UIType.Panel.
///
public object UI;
///
- /// Collection bound to DataGrid used byu plugins with UIType.Basic.
+ /// Collection bound to DataGrid used by plugins with UIType.Basic.
/// Objects in collection should be of a class defined within the plugin consisting of string properties. Each object is a single row, and the property names are used as column headers.
///
public ObservableCollection DataGrid;
@@ -32,7 +32,7 @@ namespace Observatory.Framework
/// Instantiate PluginUI of UIType.Basic.
///
///
- /// Collection bound to DataGrid used byu plugins with UIType.Basic.
+ /// Collection bound to DataGrid used by plugins with UIType.Basic.
/// Objects in collection should be of a class defined within the plugin consisting of string properties. Each object is a single row, and the property names are used as column headers.
///
public PluginUI(ObservableCollection DataGrid)
diff --git a/ObservatoryHerald/ObservatoryHerald.csproj b/ObservatoryHerald/ObservatoryHerald.csproj
index 18db54b..312ef56 100644
--- a/ObservatoryHerald/ObservatoryHerald.csproj
+++ b/ObservatoryHerald/ObservatoryHerald.csproj
@@ -4,6 +4,7 @@
net6.0
enable
true
+ Debug;Release;Portable