2
0
mirror of https://github.com/9ParsonsB/Pulsar.git synced 2025-04-05 17:39:39 -04:00
pulsar/ObservatoryCore/UI/Views/BasicUIView.axaml.cs
2023-05-05 09:38:00 -02:30

1247 lines
51 KiB
C#

using Avalonia;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Markup.Xaml;
using System.Text.RegularExpressions;
using Observatory.Framework;
using Observatory.Framework.Interfaces;
using System.Linq;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using Avalonia.Media;
using Avalonia.Controls.ApplicationLifetimes;
using System.Runtime.InteropServices;
using System.IO;
namespace Observatory.UI.Views
{
public class BasicUIView : UserControl
{
private DataGrid dataGrid;
private NativeNotification.NativePopup nativePopup;
public BasicUIView()
{
InitializeComponent();
nativePopup = new();
this.DetachedFromVisualTree += (o, e) =>
{
nativePopup.CloseAll();
};
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public static readonly DirectProperty<BasicUIView, PluginUI.UIType> UITypeProperty =
AvaloniaProperty.RegisterDirect<BasicUIView, PluginUI.UIType>(
nameof(UIType),
o => o.UIType,
(o, v) => o.UIType = v,
PluginUI.UIType.None,
BindingMode.OneWay
);
public PluginUI.UIType UIType
{
get
{
return _uitype;
}
set
{
_uitype = value;
UITypeChange();
}
}
private PluginUI.UIType _uitype;
private void ColumnGeneration(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
e.Column.Header = SplitCamelCase(e.PropertyName);
e.Column.CanUserReorder = true;
e.Column.CanUserResize = true;
e.Column.CanUserSort = true;
}
private void UITypeChange()
{
var uiPanel = this.Find<Panel>("UIPanel");
dataGrid = null;
switch (UIType)
{
case PluginUI.UIType.None:
break;
case PluginUI.UIType.Basic:
dataGrid = new()
{
[!DataGrid.ItemsProperty] = new Binding("BasicUIGrid"),
SelectionMode = DataGridSelectionMode.Extended,
GridLinesVisibility = DataGridGridLinesVisibility.Vertical,
AutoGenerateColumns = true,
IsReadOnly = true
};
dataGrid.AutoGeneratingColumn += ColumnGeneration;
dataGrid.DataContextChanged += OnDataContextSet;
dataGrid.SelectionChanged += OnSelectionChanged;
uiPanel.Children.Clear();
uiPanel.Children.Add(dataGrid);
break;
case PluginUI.UIType.Avalonia:
//TODO: Implement plugins with full Avalonia UI.
throw new NotImplementedException();
case PluginUI.UIType.Core:
uiPanel.Children.Clear();
ScrollViewer scrollViewer = new();
scrollViewer.Content = GenerateCoreUI();
uiPanel.Children.Add(scrollViewer);
break;
default:
break;
}
}
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
((Observatory.UI.ViewModels.BasicUIViewModel)dataGrid.DataContext).SelectedItems = dataGrid.SelectedItems;
}
private void OnDataContextSet(object sender, EventArgs e)
{
if (UIType != PluginUI.UIType.Basic || !(sender is DataGrid)) return;
dataGrid = (DataGrid)sender;
if (dataGrid.DataContext != null)
{
var dataContext = ((ViewModels.BasicUIViewModel)dataGrid.DataContext).BasicUIGrid;
dataContext.CollectionChanged += ScrollToLast;
}
}
private void ScrollToLast(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
// Only trigger on adds.
if (e.Action != System.Collections.Specialized.NotifyCollectionChangedAction.Add || UIType != PluginUI.UIType.Basic || dataGrid == null || !(sender is ObservableCollection<object>))
return;
var dataContext = (ObservableCollection<object>)sender;
Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() =>
{
dataGrid.ScrollIntoView(dataContext[dataContext.Count - 1], null);
});
}
private Grid GenerateCoreUI()
{
Grid corePanel = new();
ColumnDefinitions columns = new()
{
new ColumnDefinition() { Width = new GridLength(0, GridUnitType.Auto) },
new ColumnDefinition() { Width = new GridLength(300) },
new ColumnDefinition() { Width = new GridLength(0, GridUnitType.Auto) }
};
corePanel.ColumnDefinitions = columns;
SettingGridManager gridManager = new(corePanel);
var pluginManager = PluginManagement.PluginManager.GetInstance;
#region Native Settings
#region Plugin List
DataGrid pluginList = new() { Margin = new Thickness(0, 20, 0, 0) };
pluginList.Columns.Add(new DataGridTextColumn()
{
Header = "Plugin",
Binding = new Binding("Name")
});
pluginList.Columns.Add(new DataGridTextColumn()
{
Header = "Types",
Binding = new Binding("TypesString")
});
pluginList.Columns.Add(new DataGridTextColumn()
{
Header = "Version",
Binding = new Binding("Version")
});
pluginList.Columns.Add(new DataGridTextColumn()
{
Header = "Status",
Binding = new Binding("Status")
});
Dictionary<IObservatoryPlugin, PluginView> uniquePlugins = new();
foreach (var (plugin, signed) in pluginManager.workerPlugins)
{
if (!uniquePlugins.ContainsKey(plugin))
{
uniquePlugins.Add(plugin,
new PluginView() { Name = plugin.Name, Types = new() { "Worker" }, Version = plugin.Version, Status = GetStatusText(signed) });
}
}
foreach (var (plugin, signed) in pluginManager.notifyPlugins)
{
if (!uniquePlugins.ContainsKey(plugin))
{
uniquePlugins.Add(plugin,
new PluginView() { Name = plugin.Name, Types = new() { "Notifier" }, Version = plugin.Version, Status = GetStatusText(signed) });
}
else
{
uniquePlugins[plugin].Types.Add("Notifier");
}
}
pluginList.Items = uniquePlugins.Values;
gridManager.AddSetting(pluginList);
Button pluginFolderButton = new()
{
Content = "Open Plugin Folder",
Height = 30,
Width = 150,
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right,
HorizontalContentAlignment = Avalonia.Layout.HorizontalAlignment.Center,
Margin = new Thickness(0, 0, 0, 20)
};
pluginFolderButton.Click += (object sender, RoutedEventArgs e) =>
{
string pluginDir = AppDomain.CurrentDomain.BaseDirectory + "plugins";
if (!Directory.Exists(pluginDir))
{
Directory.CreateDirectory(pluginDir);
}
var fileExplorerInfo = new System.Diagnostics.ProcessStartInfo() { FileName = pluginDir, UseShellExecute = true };
System.Diagnostics.Process.Start(fileExplorerInfo);
};
gridManager.AddSetting(pluginFolderButton);
#endregion
#region Popup Notification settings
Expander notificationExpander = new()
{
Header = "Popup Notifications",
DataContext = Properties.Core.Default,
Margin = new Thickness(0, 0)
};
Grid notificationGrid = new() { Margin = new Thickness(10, 10) };
notificationGrid.ColumnDefinitions = new()
{
new ColumnDefinition() { Width = new GridLength(0, GridUnitType.Star) },
new ColumnDefinition() { Width = new GridLength(3, GridUnitType.Star) },
new ColumnDefinition() { Width = new GridLength(3, GridUnitType.Star) }
};
notificationGrid.RowDefinitions = new();
SettingGridManager notificationGridManager = new(notificationGrid);
TextBlock nativeNotifyLabel = new() { Text = "Enabled" };
CheckBox nativeNotifyCheckbox = new() { IsChecked = Properties.Core.Default.NativeNotify, Content = nativeNotifyLabel };
nativeNotifyCheckbox.Checked += (object sender, RoutedEventArgs e) =>
{
Properties.Core.Default.NativeNotify = true;
Properties.Core.Default.Save();
};
nativeNotifyCheckbox.Unchecked += (object sender, RoutedEventArgs e) =>
{
Properties.Core.Default.NativeNotify = false;
Properties.Core.Default.Save();
};
Button notifyTestButton = new()
{
Content = "Test",
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right
};
notifyTestButton.Click += (object sender, RoutedEventArgs e) =>
{
Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() =>
{
var notificationArgs = new NotificationArgs()
{
Title = "Test Notification",
Detail = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras suscipit hendrerit libero ac scelerisque."
};
nativePopup.InvokeNativeNotification(notificationArgs);
});
};
TextBlock notifyFontLabel = new()
{
Text = "Font: ",
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
};
ComboBox notifyFontDropDown = new()
{
MinWidth = 200
};
notifyFontDropDown.Items = new System.Drawing.Text.InstalledFontCollection().Families.Select(font => font.Name);
if (Properties.Core.Default.NativeNotifyFont.Length > 0)
{
notifyFontDropDown.SelectedItem = Properties.Core.Default.NativeNotifyFont;
}
notifyFontDropDown.SelectionChanged += (object sender, SelectionChangedEventArgs e) =>
{
var comboBox = (ComboBox)sender;
Properties.Core.Default.NativeNotifyFont = comboBox.SelectedItem.ToString();
Properties.Core.Default.Save();
};
TextBlock monitorLabel = new()
{
Text = "Display: ",
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
};
ComboBox monitorDropDown = new()
{
MinWidth = 200
};
List<string> displays = new();
displays.Add("Primary");
var application = (IClassicDesktopStyleApplicationLifetime)Application.Current.ApplicationLifetime;
var screens = application.MainWindow.Screens.All;
if (screens.Count > 1)
for (int i = 0; i < screens.Count; i++)
{
displays.Add((i + 1).ToString());
}
monitorDropDown.Items = displays;
if (Properties.Core.Default.NativeNotifyScreen == -1)
{
monitorDropDown.SelectedItem = "Primary";
}
else
{
monitorDropDown.SelectedItem = (Properties.Core.Default.NativeNotifyScreen).ToString();
}
monitorDropDown.SelectionChanged += (object sender, SelectionChangedEventArgs e) =>
{
var comboBox = (ComboBox)sender;
string selectedItem = comboBox.SelectedItem.ToString();
int selectedScreen = selectedItem == "Primary" ? -1 : Int32.Parse(selectedItem);
Properties.Core.Default.NativeNotifyScreen = selectedScreen;
Properties.Core.Default.Save();
};
TextBlock cornerLabel = new()
{
Text = "Corner: ",
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
};
ComboBox cornerDropDown = new()
{
MinWidth = 200
};
List<string> corners = new()
{
"Bottom-Right",
"Bottom-Left",
"Top-Right",
"Top-Left"
};
cornerDropDown.Items = corners;
cornerDropDown.SelectedItem = corners[Properties.Core.Default.NativeNotifyCorner];
cornerDropDown.SelectionChanged += (object sender, SelectionChangedEventArgs e) =>
{
var comboBox = (ComboBox)sender;
Properties.Core.Default.NativeNotifyCorner = comboBox.SelectedIndex;
Properties.Core.Default.Save();
};
TextBlock colourLabel = new()
{
Text = "Colour: ",
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
};
BrushConverter brushConverter = new();
Egorozh.ColorPicker.Dialog.ColorPickerButton colourPickerButton = new()
{
Width = 25,
Height = 25,
Color = Color.FromUInt32(Properties.Core.Default.NativeNotifyColour),
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Left
};
colourPickerButton.PropertyChanged += (object sender, AvaloniaPropertyChangedEventArgs e) =>
{
if (e.Property.Name == "Color")
{
Properties.Core.Default.NativeNotifyColour = ((Color)e.NewValue).ToUint32();
Properties.Core.Default.Save();
}
};
TextBlock scaleLabel = new()
{
Text = "Scale (%): ",
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
};
NumericUpDown scaleSpinner = new()
{
Value = Properties.Core.Default.NativeNotifyScale,
AllowSpin = true,
Minimum = 1,
Maximum = 1000,
Increment = 1,
Width = 200,
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Left
};
scaleSpinner.ValueChanged += (object sender, NumericUpDownValueChangedEventArgs e) =>
{
Properties.Core.Default.NativeNotifyScale = Convert.ToInt32(e.NewValue);
Properties.Core.Default.Save();
};
TextBlock timeoutLabel = new()
{
Text = "Duration (ms): ",
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
};
NumericUpDown timeoutSpinner = new()
{
Value = Properties.Core.Default.NativeNotifyTimeout,
AllowSpin = true,
Minimum = 1,
Maximum = 3600000,
Increment = 1,
Width = 200,
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Left
};
timeoutSpinner.ValueChanged += (object sender, NumericUpDownValueChangedEventArgs e) =>
{
Properties.Core.Default.NativeNotifyTimeout = Convert.ToInt32(e.NewValue);
Properties.Core.Default.Save();
};
notificationGridManager.AddSettingWithLabel(monitorLabel, monitorDropDown);
notificationGridManager.AddSettingWithLabel(cornerLabel, cornerDropDown);
notificationGridManager.AddSettingWithLabel(notifyFontLabel, notifyFontDropDown);
notificationGridManager.AddSettingWithLabel(scaleLabel, scaleSpinner);
notificationGridManager.AddSettingWithLabel(timeoutLabel, timeoutSpinner);
notificationGridManager.AddSettingWithLabel(colourLabel, colourPickerButton);
notificationGridManager.AddSettingSameLine(notifyTestButton);
notificationGridManager.AddSetting(nativeNotifyCheckbox);
notificationExpander.Content = notificationGrid;
gridManager.AddSetting(notificationExpander);
#endregion
#region Voice Notification Settings
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
Expander voiceExpander = new()
{
Header = "Voice Notifications",
DataContext = Properties.Core.Default,
Margin = new Thickness(0, 0)
};
Grid voiceGrid = new() { Margin = new Thickness(10, 10) };
SettingGridManager voiceGridManager = new(voiceGrid);
voiceGrid.ColumnDefinitions = new()
{
new ColumnDefinition() { Width = new GridLength(0, GridUnitType.Star) },
new ColumnDefinition() { Width = new GridLength(3, GridUnitType.Star) },
new ColumnDefinition() { Width = new GridLength(3, GridUnitType.Star) }
};
TextBlock voiceLabel = new() { Text = "Enabled" };
CheckBox voiceCheckbox = new() { IsChecked = Properties.Core.Default.VoiceNotify, Content = voiceLabel };
voiceCheckbox.Checked += (object sender, RoutedEventArgs e) =>
{
Properties.Core.Default.VoiceNotify = true;
Properties.Core.Default.Save();
};
voiceCheckbox.Unchecked += (object sender, RoutedEventArgs e) =>
{
Properties.Core.Default.VoiceNotify = false;
Properties.Core.Default.Save();
};
Button voiceTestButton = new()
{
Content = "Test",
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right
};
voiceTestButton.Click += (object sender, RoutedEventArgs e) =>
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Properties.Core.Default.VoiceSelected.Length > 0)
{
List<string> harvardSentences = new()
{
"Oak is strong and also gives shade.",
"Cats and dogs each hate the other.",
"The pipe began to rust while new.",
"Open the crate but don't break the glass.",
"Add the sum to the product of these three.",
"Thieves who rob friends deserve jail.",
"The ripe taste of cheese improves with age.",
"Act on these orders with great speed.",
"The hog crawled under the high fence.",
"Move the vat over the hot fire."
};
NotificationArgs args = new()
{
Title = "Speech Synthesis Test",
TitleSsml = "<speak version=\"1.0\" xmlns=\"http://www.w3.org/2001/10/synthesis\" xml:lang=\"en-US\"><voice name=\"\">Speech Synthesis Test</voice></speak>",
Detail = harvardSentences.OrderBy(s => new Random().NextDouble()).First()
};
new NativeNotification.NativeVoice().EnqueueAndAnnounce(args);
}
};
TextBlock voiceSelectionLabel = new()
{
Text = "Voice: ",
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
};
ComboBox voiceSelectionDropDown = new()
{
MinWidth = 200
};
var voices = new System.Speech.Synthesis.SpeechSynthesizer().GetInstalledVoices();
voiceSelectionDropDown.Items = voices.Select(v => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? v.VoiceInfo.Name : string.Empty);
if (Properties.Core.Default.VoiceSelected.Length > 0)
{
voiceSelectionDropDown.SelectedItem = Properties.Core.Default.VoiceSelected;
}
voiceSelectionDropDown.SelectionChanged += (object sender, SelectionChangedEventArgs e) =>
{
var comboBox = (ComboBox)sender;
Properties.Core.Default.VoiceSelected = comboBox.SelectedItem.ToString();
Properties.Core.Default.Save();
};
TextBlock voiceVolumeLabel = new()
{
Text = "Volume: ",
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
};
Slider voiceVolume = new()
{
Value = Properties.Core.Default.VoiceVolume,
Height = 40,
Width = 300,
Minimum = 0,
Maximum = 100,
Padding = new Thickness(0,0,0,20),
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
};
voiceVolume.PropertyChanged += (object sender, AvaloniaPropertyChangedEventArgs e) =>
{
if (e.Property == Slider.ValueProperty)
{
Properties.Core.Default.VoiceVolume = Convert.ToInt32(e.NewValue);
Properties.Core.Default.Save();
}
};
TextBlock voiceRateLabel = new()
{
Text = "Speed: ",
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
};
Slider voiceRate = new()
{
Value = Properties.Core.Default.VoiceRate,
Height = 40,
Width = 300,
Minimum = -10,
Maximum = 10,
Padding = new Thickness(0, 0, 0, 20),
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
};
voiceRate.PropertyChanged += (object sender, AvaloniaPropertyChangedEventArgs e) =>
{
if (e.Property == Slider.ValueProperty)
{
Properties.Core.Default.VoiceRate = Convert.ToInt32(e.NewValue);
Properties.Core.Default.Save();
}
};
voiceGridManager.AddSettingWithLabel(voiceVolumeLabel, voiceVolume);
voiceGridManager.AddSettingWithLabel(voiceRateLabel, voiceRate);
voiceGridManager.AddSettingWithLabel(voiceSelectionLabel, voiceSelectionDropDown);
voiceGridManager.AddSetting(voiceCheckbox);
voiceGridManager.AddSettingSameLine(voiceTestButton);
voiceExpander.Content = voiceGrid;
gridManager.AddSetting(voiceExpander);
}
#endregion
#region Export options
Expander exportExpander = new()
{
Header = "Export Options",
DataContext = Properties.Core.Default,
Margin = new Thickness(0, 0)
};
Grid exportGrid = new() { Margin = new Thickness(10, 10) };
SettingGridManager exportGridManager = new(exportGrid);
exportGrid.ColumnDefinitions = new()
{
new ColumnDefinition() { Width = new GridLength(0, GridUnitType.Star) },
new ColumnDefinition() { Width = new GridLength(3, GridUnitType.Star) },
new ColumnDefinition() { Width = new GridLength(3, GridUnitType.Star) }
};
TextBlock exportStyleLabel = new()
{
Text = "Export style: ",
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
};
ComboBox exportStyleDropDown = new()
{
MinWidth = 200
};
exportStyleDropDown.Items = new List<string>() {
"Fixed width",
"Tab separated",
};
if (Properties.Core.Default.ExportStyle.Length > 0)
{
exportStyleDropDown.SelectedItem = Properties.Core.Default.ExportStyle;
}
exportStyleDropDown.SelectionChanged += (object sender, SelectionChangedEventArgs e) =>
{
var comboBox = (ComboBox)sender;
Properties.Core.Default.ExportStyle = comboBox.SelectedItem.ToString();
Properties.Core.Default.Save();
};
TextBlock exportPathLabel = new()
{
Text = "Export Path: ",
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
};
TextBox exportPath = new()
{
Text = Properties.Core.Default.ExportFolder
};
Button exportBrowse = new()
{
Content = "Browse",
Height = 30,
Width = 100,
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right,
HorizontalContentAlignment = Avalonia.Layout.HorizontalAlignment.Center
};
exportBrowse.Click += (object source, RoutedEventArgs e) =>
{
OpenFolderDialog openFolderDialog = new()
{
Directory = exportPath.Text
};
var browseTask = openFolderDialog.ShowAsync((Window)((Button)source).GetVisualRoot());
browseTask.ContinueWith((task) =>
{
string path = task.Result;
if (path != string.Empty)
{
Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() => { exportPath.Text = path; });
Properties.Core.Default.ExportFolder = path;
Properties.Core.Default.Save();
}
});
};
exportPath.LostFocus += (object sender, RoutedEventArgs e) =>
{
if (System.IO.Directory.Exists(exportPath.Text))
{
Properties.Core.Default.ExportFolder = exportPath.Text;
Properties.Core.Default.Save();
}
};
exportGridManager.AddSettingWithLabel(exportStyleLabel, exportStyleDropDown);
exportGridManager.AddSettingWithLabel(exportPathLabel, exportPath);
exportGridManager.AddSetting(exportBrowse);
exportExpander.Content = exportGrid;
gridManager.AddSetting(exportExpander);
#endregion
#region System Context Priming setting
TextBlock primeSystemContextLabel = new() { Text = "Try re-load current system information when starting monitor" };
CheckBox primeSystemContexCheckbox = new() { IsChecked = Properties.Core.Default.TryPrimeSystemContextOnStartMonitor, Content = primeSystemContextLabel };
primeSystemContexCheckbox.Checked += (object sender, RoutedEventArgs e) =>
{
Properties.Core.Default.TryPrimeSystemContextOnStartMonitor = true;
Properties.Core.Default.Save();
};
primeSystemContexCheckbox.Unchecked += (object sender, RoutedEventArgs e) =>
{
Properties.Core.Default.TryPrimeSystemContextOnStartMonitor = false;
Properties.Core.Default.Save();
};
#endregion
#region Actions On Launch
TextBlock startMonitorLabel = new() { Text = "Start monitor on Observatory launch" };
CheckBox startMonitorCheckbox = new() { IsChecked = Properties.Core.Default.StartMonitor, Content = startMonitorLabel };
startMonitorCheckbox.Checked += (object sender, RoutedEventArgs e) =>
{
Properties.Core.Default.StartMonitor = true;
Properties.Core.Default.Save();
};
startMonitorCheckbox.Unchecked += (object sender, RoutedEventArgs e) =>
{
Properties.Core.Default.StartMonitor = false;
Properties.Core.Default.Save();
};
TextBlock startReadAllLabel = new() { Text = "Read All on Observatory launch" };
CheckBox startReadAllCheckbox = new() { IsChecked = Properties.Core.Default.StartReadAll, Content = startReadAllLabel };
startReadAllCheckbox.Checked += (object sender, RoutedEventArgs e) =>
{
Properties.Core.Default.StartReadAll = true;
Properties.Core.Default.Save();
};
startReadAllCheckbox.Unchecked += (object sender, RoutedEventArgs e) =>
{
Properties.Core.Default.StartReadAll = false;
Properties.Core.Default.Save();
};
#endregion
#endregion
#region Journal Location
TextBlock journalPathLabel = new()
{
Text = "Journal Path: ",
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
};
TextBox journalPath = new()
{
Text = Properties.Core.Default.JournalFolder
};
Button journalBrowse = new()
{
Content = "Browse",
Height = 30,
Width = 100,
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right,
HorizontalContentAlignment = Avalonia.Layout.HorizontalAlignment.Center
};
journalBrowse.Click += (object source, RoutedEventArgs e) =>
{
OpenFolderDialog openFolderDialog = new()
{
Directory = journalPath.Text
};
var browseTask = openFolderDialog.ShowAsync((Window)((Button)source).GetVisualRoot());
browseTask.ContinueWith((task) =>
{
string path = task.Result;
if (path != string.Empty)
{
Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() => { journalPath.Text = path; });
Properties.Core.Default.JournalFolder = path;
Properties.Core.Default.Save();
}
});
};
journalPath.LostFocus += (object sender, RoutedEventArgs e) =>
{
if (System.IO.Directory.Exists(journalPath.Text))
{
Properties.Core.Default.JournalFolder = journalPath.Text;
Properties.Core.Default.Save();
}
};
#endregion
#region Plugin Settings
foreach(var plugin in uniquePlugins.Keys)
{
GeneratePluginSettingUI(corePanel, plugin);
}
#endregion
gridManager.AddSetting(primeSystemContexCheckbox);
gridManager.AddSetting(startMonitorCheckbox);
gridManager.AddSetting(startReadAllCheckbox);
gridManager.AddSettingWithLabel(journalPathLabel, journalPath);
gridManager.AddSetting(journalBrowse);
return corePanel;
}
private void GeneratePluginSettingUI(Grid gridPanel, IObservatoryPlugin plugin)
{
var displayedSettings = PluginManagement.PluginManager.GetSettingDisplayNames(plugin.Settings);
if (displayedSettings.Count > 0)
{
Expander expander = new()
{
Header = plugin.Name,
DataContext = plugin.Settings,
Margin = new Thickness(0, 0)
};
Grid settingsGrid = new() { Margin = new Thickness(10,10) };
ColumnDefinitions settingColumns = new()
{
new ColumnDefinition() { Width = new GridLength(3, GridUnitType.Star) },
new ColumnDefinition() { Width = new GridLength(3, GridUnitType.Star) },
new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) }
};
settingsGrid.ColumnDefinitions = settingColumns;
expander.Content = settingsGrid;
int nextRow = gridPanel.RowDefinitions.Count;
gridPanel.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(0, GridUnitType.Auto) });
gridPanel.AddControl(expander, nextRow, 0, 3);
var nonIgnoredSettings = displayedSettings.Where(s => !Attribute.IsDefined(s.Key, typeof(SettingIgnore)));
foreach (var setting in nonIgnoredSettings)
{
if (setting.Key.PropertyType != typeof(bool) || settingsGrid.Children.Count % 2 == 0)
{
settingsGrid.RowDefinitions.Add(new RowDefinition()
{
Height = new GridLength(setting.Key.PropertyType != typeof(bool) ? 40 : 25),
});
}
TextBlock label = new() { Text = setting.Value, VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center };
switch (setting.Key.GetValue(plugin.Settings))
{
case bool boolSetting:
CheckBox checkBox = new() { IsChecked = boolSetting, Content = label };
checkBox.Checked += (object sender, RoutedEventArgs e) =>
{
setting.Key.SetValue(plugin.Settings, true);
PluginManagement.PluginManager.GetInstance.SaveSettings(plugin, plugin.Settings);
};
checkBox.Unchecked += (object sender, RoutedEventArgs e) =>
{
setting.Key.SetValue(plugin.Settings, false);
PluginManagement.PluginManager.GetInstance.SaveSettings(plugin, plugin.Settings);
};
settingsGrid.AddControl(checkBox, settingsGrid.RowDefinitions.Count - 1, settingsGrid.Children.Count % 2 == 0 ? 0 : 1);
break;
case string stringSetting:
TextBox textBox = new() { Text = stringSetting };
settingsGrid.AddControl(label, settingsGrid.RowDefinitions.Count - 1, 0);
settingsGrid.AddControl(textBox, settingsGrid.RowDefinitions.Count - 1, 1);
textBox.LostFocus += (object sender, RoutedEventArgs e) =>
{
setting.Key.SetValue(plugin.Settings, ((TextBox)sender).Text);
PluginManagement.PluginManager.GetInstance.SaveSettings(plugin, plugin.Settings);
};
break;
case int intSetting:
// 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.
Control intControl;
SettingNumericBounds bounds = (SettingNumericBounds)System.Attribute.GetCustomAttribute(setting.Key, typeof(SettingNumericBounds));
if (System.Attribute.IsDefined(setting.Key, typeof(SettingNumericUseSlider)))
{
// TODO: Suss the contents of this block into a function to share with non-integral numeric types as well?
Slider slider = new()
{
Value = intSetting,
Height = 40,
Width = 300,
};
if (bounds != null)
{
slider.Minimum = bounds.Minimum;
slider.Maximum = bounds.Maximum;
};
slider.PropertyChanged += (object sender, AvaloniaPropertyChangedEventArgs e) =>
{
if (e.Property == Slider.ValueProperty)
{
setting.Key.SetValue(plugin.Settings, Convert.ToInt32(e.NewValue));
PluginManagement.PluginManager.GetInstance.SaveSettings(plugin, plugin.Settings);
}
};
intControl = slider;
}
else // Use a Numeric Up/Down
{
NumericUpDown numericUpDown = new() { Value = intSetting, AllowSpin = true };
if (bounds != null)
{
numericUpDown.Minimum = (decimal)bounds.Minimum;
numericUpDown.Maximum = (decimal)bounds.Maximum;
numericUpDown.Increment = (decimal)bounds.Increment;
}
numericUpDown.ValueChanged += (object sender, NumericUpDownValueChangedEventArgs e) =>
{
setting.Key.SetValue(plugin.Settings, Convert.ToInt32(e.NewValue));
PluginManagement.PluginManager.GetInstance.SaveSettings(plugin, plugin.Settings);
};
intControl = numericUpDown;
}
settingsGrid.AddControl(label, settingsGrid.RowDefinitions.Count - 1, 0);
settingsGrid.AddControl(intControl, settingsGrid.RowDefinitions.Count - 1, 1);
break;
case FileInfo fileSetting:
label.Text += ": ";
TextBox settingPath = new()
{
Text = fileSetting.FullName,
Width = 300,
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right
};
Button settingBrowse = new()
{
Content = "Browse",
Height = 30,
Width = 100,
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Left,
HorizontalContentAlignment = Avalonia.Layout.HorizontalAlignment.Center
};
settingBrowse.Click += (object source, RoutedEventArgs e) =>
{
OpenFileDialog openFileDialog = new()
{
Directory = fileSetting.DirectoryName,
AllowMultiple = false
};
var browseTask = openFileDialog.ShowAsync((Window)((Button)source).GetVisualRoot());
browseTask.ContinueWith((task) =>
{
if (task.Result?.Count() > 0)
{
string path = browseTask.Result[0];
Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() => { settingPath.Text = path; });
setting.Key.SetValue(plugin.Settings, new FileInfo(path));
PluginManagement.PluginManager.GetInstance.SaveSettings(plugin, plugin.Settings);
}
});
};
settingPath.LostFocus += (object sender, RoutedEventArgs e) =>
{
if (settingPath.Text.Trim() != string.Empty)
{
string fullPath;
try
{
fullPath = Path.GetFullPath(settingPath.Text);
}
catch
{
fullPath = string.Empty;
}
setting.Key.SetValue(plugin.Settings, new FileInfo(fullPath));
PluginManagement.PluginManager.GetInstance.SaveSettings(plugin, plugin.Settings);
}
};
StackPanel stackPanel = new() { Orientation = Avalonia.Layout.Orientation.Horizontal };
stackPanel.Children.Add(label);
stackPanel.Children.Add(settingPath);
settingsGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(32) });
settingsGrid.AddControl(stackPanel, settingsGrid.RowDefinitions.Count - 1, 0, 2);
settingsGrid.AddControl(settingBrowse, settingsGrid.RowDefinitions.Count - 1, 2);
break;
case Action action:
Button actionButton = new()
{
Content = label.Text,
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Left
};
actionButton.Click += (object sender, RoutedEventArgs e) =>
{
action.Invoke();
//Possible for the action to have changed a setting, save just in case.
PluginManagement.PluginManager.GetInstance.SaveSettings(plugin, plugin.Settings);
};
settingsGrid.AddControl(actionButton, settingsGrid.RowDefinitions.Count - 1, 0);
break;
case Dictionary<string, object> dictSetting:
var backingValueName = (SettingBackingValue)Attribute.GetCustomAttribute(setting.Key, typeof(SettingBackingValue));
var backingValue = from s in displayedSettings
where s.Value == backingValueName.BackingProperty
select s.Key;
if (backingValue.Count() != 1)
throw new($"{plugin.ShortName}: Dictionary settings must have exactly one backing value.");
label.Text += ": ";
ComboBox selectionDropDown = new()
{
MinWidth = 200
};
selectionDropDown.Items = from s in dictSetting
orderby s.Key
select s.Key;
string currentSelection = backingValue.First().GetValue(plugin.Settings)?.ToString();
if (currentSelection?.Length > 0)
{
selectionDropDown.SelectedItem = currentSelection;
}
selectionDropDown.SelectionChanged += (object sender, SelectionChangedEventArgs e) =>
{
var comboBox = (ComboBox)sender;
backingValue.First().SetValue(plugin.Settings, comboBox.SelectedItem.ToString());
PluginManagement.PluginManager.GetInstance.SaveSettings(plugin, plugin.Settings);
};
settingsGrid.AddControl(label, settingsGrid.RowDefinitions.Count - 1, 0);
settingsGrid.AddControl(selectionDropDown, settingsGrid.RowDefinitions.Count - 1, 1);
break;
}
}
}
}
private string GetStatusText(PluginManagement.PluginManager.PluginStatus status)
{
string statusText;
switch (status)
{
case PluginManagement.PluginManager.PluginStatus.Signed:
statusText = "Signed";
break;
case PluginManagement.PluginManager.PluginStatus.Unsigned:
statusText = "Unsigned";
break;
case PluginManagement.PluginManager.PluginStatus.InvalidSignature:
statusText = "Signature Invalid";
break;
case PluginManagement.PluginManager.PluginStatus.InvalidPlugin:
statusText = "No Interface";
break;
case PluginManagement.PluginManager.PluginStatus.InvalidLibrary:
statusText = "Invalid Library";
break;
default:
statusText = "Unknown";
break;
}
return statusText;
}
//From https://stackoverflow.com/questions/5796383/insert-spaces-between-words-on-a-camel-cased-token
private static string SplitCamelCase(string str)
{
return Regex.Replace(
Regex.Replace(
str,
@"(\P{Ll})(\P{Ll}\p{Ll})",
"$1 $2"
),
@"(\p{Ll})(\P{Ll})",
"$1 $2"
);
}
}
internal class PluginView
{
public string Name { get; set; }
public HashSet<string> Types { get; set; }
public string TypesString
{
get => string.Join(", ", Types);
set { }
}
public string Version { get; set; }
public string Status { get; set; }
}
internal static class GridExtention
{
public static void AddControl(this Grid grid, Control control, int row, int column, int span = 1)
{
grid.Children.Add(control);
Grid.SetColumnSpan(control, span);
Grid.SetColumn(control, column);
Grid.SetRow(control, row);
}
}
internal class SettingGridManager
{
private Grid settingPanel;
public SettingGridManager(Grid settingPanel)
{
this.settingPanel = settingPanel;
}
public int NewRow()
{
settingPanel.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(0, GridUnitType.Auto) });
return settingPanel.RowDefinitions.Count - 1;
}
public void AddSetting(Control control)
{
int rowIndex = NewRow();
settingPanel.AddControl(control, rowIndex, 0, 2);
}
public void AddSettingSameLine(Control control)
{
int rowIndex = settingPanel.RowDefinitions.Count - 1;
settingPanel.AddControl(control, rowIndex, 0, 2);
}
public void AddSettingWithLabel(Control label, Control control)
{
int rowIndex = NewRow();
settingPanel.AddControl(label, rowIndex, 0);
settingPanel.AddControl(control, rowIndex, 1, 2);
}
}
}