mirror of
https://github.com/SabreTools/MPF.git
synced 2026-07-02 17:24:48 +00:00
LogWindow -> LogOutput
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
- Add internal support for 3- and 4-layer discs
|
||||
- Revamp disc information window
|
||||
- Add PIC layerbreak extraction
|
||||
- Overhaul main window and logging panel
|
||||
|
||||
### 1.18 (2020-11-10)
|
||||
- Add more information extraction and generation for Aaru
|
||||
|
||||
@@ -10,8 +10,6 @@ namespace MPF
|
||||
/// </summary>
|
||||
public static class Constants
|
||||
{
|
||||
public const int LogWindowMarginFromMainWindow = 10;
|
||||
|
||||
// Private lists of known drive speed ranges
|
||||
private static IReadOnlyList<int> cd { get; } = new List<int> { 1, 2, 3, 4, 6, 8, 12, 16, 20, 24, 32, 40, 44, 48, 52, 56, 72 };
|
||||
private static IReadOnlyList<int> dvd { get; } = cd.Where(s => s <= 24).ToList();
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<Window x:Class="MPF.Windows.LogWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:MPF"
|
||||
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
|
||||
Title="Log Window" Height="350" Width="600" Closed="OnWindowClosed" Loaded="OnWindowLoaded"
|
||||
>
|
||||
<UserControl x:Class="MPF.UserControls.LogOutput"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:MPF"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="40" />
|
||||
@@ -45,12 +46,8 @@
|
||||
IsChecked="{Binding Path=OpenLogWindowAtStartup}"
|
||||
/>
|
||||
<Button Margin="0 0 10 10" Grid.Column="3" Height="22" Width="60" Content="Clear" HorizontalAlignment="Right" Click="OnClearButton" />
|
||||
<Button Margin="0 0 10 10" Grid.Column="4" Height="22" Width="60" Content="Hide" HorizontalAlignment="Right" Click="OnHideButton" />
|
||||
<Button Margin="0 0 10 10" Grid.Column="5" Height="22" Width="60" Content="Save" HorizontalAlignment="Right" Click="OnSaveButton" />
|
||||
<Button Visibility="Hidden" Name="AbortButton" Margin="0 0 10 10" Grid.Column="2" Height="22" Width="80" Content="Abort" HorizontalAlignment="Right" Click="OnAbortButton" />
|
||||
</Grid>
|
||||
|
||||
|
||||
|
||||
</Grid>
|
||||
</Window>
|
||||
</UserControl>
|
||||
@@ -2,40 +2,28 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace MPF.Windows
|
||||
namespace MPF.UserControls
|
||||
{
|
||||
public partial class LogWindow : Window
|
||||
public partial class LogOutput : UserControl
|
||||
{
|
||||
private const int GWL_STYLE = -16;
|
||||
private const int WS_SYSMENU = 0x80000;
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||
|
||||
private readonly MainWindow _mainWindow;
|
||||
|
||||
private FlowDocument _document;
|
||||
private Paragraph _paragraph;
|
||||
private readonly List<Matcher> _matchers;
|
||||
|
||||
volatile Process _process;
|
||||
|
||||
public LogWindow(MainWindow mainWindow)
|
||||
public LogOutput()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
this._mainWindow = mainWindow;
|
||||
DataContext = this;
|
||||
|
||||
_document = new FlowDocument();
|
||||
_paragraph = new Paragraph();
|
||||
@@ -126,12 +114,6 @@ namespace MPF.Windows
|
||||
});
|
||||
}
|
||||
|
||||
public void AdjustPositionToMainWindow()
|
||||
{
|
||||
this.Left = _mainWindow.Left;
|
||||
this.Top = _mainWindow.Top + _mainWindow.Height + Constants.LogWindowMarginFromMainWindow;
|
||||
}
|
||||
|
||||
private void GracefullyTerminateProcess()
|
||||
{
|
||||
if (_process != null)
|
||||
@@ -254,6 +236,14 @@ namespace MPF.Windows
|
||||
public void Set(State state) => this.state = state;
|
||||
}
|
||||
|
||||
public void VerboseLog(string text)
|
||||
{
|
||||
if (ViewModels.OptionsViewModel.VerboseLogging)
|
||||
AppendToTextBox(text, Brushes.Yellow);
|
||||
}
|
||||
|
||||
public void VerboseLogLn(string format) => VerboseLog(format + "\n");
|
||||
|
||||
public void AppendToTextBox(string text, Brush color)
|
||||
{
|
||||
if (Application.Current.Dispatcher.CheckAccess())
|
||||
@@ -334,31 +324,12 @@ namespace MPF.Windows
|
||||
|
||||
#region EventHandlers
|
||||
|
||||
private void OnWindowClosed(object sender, EventArgs e)
|
||||
{
|
||||
GracefullyTerminateProcess();
|
||||
}
|
||||
|
||||
private void OnWindowLoaded(object sender, EventArgs e)
|
||||
{
|
||||
var hwnd = new WindowInteropHelper(this).Handle;
|
||||
SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) & ~WS_SYSMENU);
|
||||
}
|
||||
|
||||
void OnProcessExit(object sender, EventArgs e)
|
||||
{
|
||||
Dispatcher.Invoke(() => AbortButton.IsEnabled = false);
|
||||
GracefullyTerminateProcess();
|
||||
}
|
||||
|
||||
private void OnHideButton(object sender, EventArgs e)
|
||||
{
|
||||
ViewModels.LoggerViewModel.WindowVisible = false;
|
||||
//TODO: this should be bound directly to WindowVisible property in two way fashion
|
||||
// we need to study how to properly do it in XAML
|
||||
_mainWindow.ShowLogMenuItem.IsChecked = false;
|
||||
}
|
||||
|
||||
private void OnClearButton(object sender, EventArgs e)
|
||||
{
|
||||
output.Document.Blocks.Clear();
|
||||
@@ -396,6 +367,5 @@ namespace MPF.Windows
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Windows.Media;
|
||||
using MPF.Windows;
|
||||
|
||||
namespace MPF
|
||||
{
|
||||
@@ -192,39 +190,9 @@ namespace MPF
|
||||
}
|
||||
}
|
||||
|
||||
public class LoggerViewModel
|
||||
{
|
||||
private LogWindow _logWindow;
|
||||
|
||||
public void SetWindow(LogWindow logWindow) => _logWindow = logWindow;
|
||||
|
||||
public bool WindowVisible
|
||||
{
|
||||
get => _logWindow != null ? _logWindow.IsVisible : false;
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
_logWindow.AdjustPositionToMainWindow();
|
||||
_logWindow.Show();
|
||||
}
|
||||
else
|
||||
_logWindow.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
public void VerboseLog(string text)
|
||||
{
|
||||
if (ViewModels.OptionsViewModel.VerboseLogging)
|
||||
_logWindow.AppendToTextBox(text, Brushes.Yellow);
|
||||
}
|
||||
|
||||
public void VerboseLogLn(string format) => VerboseLog(format + "\n");
|
||||
}
|
||||
|
||||
public static class ViewModels
|
||||
{
|
||||
public static OptionsViewModel OptionsViewModel { get; set; }
|
||||
public static LoggerViewModel LoggerViewModel { get; set; } = new LoggerViewModel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,9 +227,6 @@ namespace MPF.Windows
|
||||
private void PopulateCategories()
|
||||
{
|
||||
var categories = Enum.GetValues(typeof(DiscCategory)).OfType<DiscCategory?>().ToList();
|
||||
|
||||
ViewModels.LoggerViewModel.VerboseLogLn($"Populating categories, {categories.Count} categories found.");
|
||||
|
||||
Categories = new List<CategoryComboBoxItem>();
|
||||
foreach (var category in categories)
|
||||
{
|
||||
@@ -246,9 +243,6 @@ namespace MPF.Windows
|
||||
private void PopulateLanguages()
|
||||
{
|
||||
var languages = Enum.GetValues(typeof(Language)).OfType<Language?>().ToList();
|
||||
|
||||
ViewModels.LoggerViewModel.VerboseLogLn($"Populating languages, {languages.Count} languages found.");
|
||||
|
||||
Languages = new List<LanguageComboBoxItem>();
|
||||
foreach (var language in languages)
|
||||
{
|
||||
@@ -265,9 +259,6 @@ namespace MPF.Windows
|
||||
private void PopulateRegions()
|
||||
{
|
||||
var regions = Enum.GetValues(typeof(Region)).OfType<Region?>().ToList();
|
||||
|
||||
ViewModels.LoggerViewModel.VerboseLogLn($"Populating regions, {regions.Count} regions found.");
|
||||
|
||||
Regions = new List<RegionComboBoxItem>();
|
||||
foreach (var region in regions)
|
||||
{
|
||||
|
||||
@@ -4,128 +4,122 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:MPF"
|
||||
xmlns:controls="clr-namespace:MPF.UserControls"
|
||||
mc:Ignorable="d"
|
||||
Title="MPF" Height="450" Width="600" WindowStartupLocation="CenterScreen" ResizeMode="CanMinimize"
|
||||
LocationChanged="MainWindowLocationChanged" Activated="MainWindowActivated" Closing="MainWindowClosing">
|
||||
Title="MPF" Width="600" WindowStartupLocation="CenterScreen" ResizeMode="CanMinimize" SizeToContent="Height">
|
||||
|
||||
<Window.Resources>
|
||||
<local:EnumDescriptionConverter x:Key="enumConverter" />
|
||||
</Window.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="4*"/>
|
||||
<ColumnDefinition Width="13*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="30"/>
|
||||
<RowDefinition Height="4*"/>
|
||||
<RowDefinition Height="1*"/>
|
||||
<RowDefinition Height="1*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<StackPanel VerticalAlignment="Top" Grid.ColumnSpan="4">
|
||||
<Menu Width="Auto" Height="20" >
|
||||
<MenuItem Header="_File">
|
||||
<MenuItem x:Name="AppExit" Header="E_xit" HorizontalAlignment="Left" Width="185" Click="AppExitClick" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="_Tools">
|
||||
<MenuItem x:Name="OptionsMenuItem" Header="_Options" HorizontalAlignment="Left" Width="185" Click="OptionsMenuItemClick" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="_Help">
|
||||
<MenuItem x:Name="About" Header="_About" HorizontalAlignment="Left" Width="185" Click="AboutClick" />
|
||||
<MenuItem x:Name="CheckForUpdatesMenuItem" Header="_Check for Updates" HorizontalAlignment="Left" Width="185" Click="CheckForUpdatesClick" />
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel VerticalAlignment="Top" Grid.ColumnSpan="4">
|
||||
<Menu Width="Auto" Height="20" >
|
||||
<MenuItem Header="_File">
|
||||
<MenuItem x:Name="AppExit" Header="E_xit" HorizontalAlignment="Left" Width="185" Click="AppExitClick" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="_Tools">
|
||||
<MenuItem x:Name="OptionsMenuItem" Header="_Options" HorizontalAlignment="Left" Width="185" Click="OptionsMenuItemClick" />
|
||||
<MenuItem x:Name="ShowLogMenuItem" Header="Show _Log Window" IsCheckable="true" Width="185" HorizontalAlignment="Left"
|
||||
DataContext="{Binding Source={x:Static local:ViewModels.LoggerViewModel}}"
|
||||
IsChecked="{Binding Path=WindowVisible}"
|
||||
/>
|
||||
</MenuItem>
|
||||
<MenuItem Header="_Help">
|
||||
<MenuItem x:Name="About" Header="_About" HorizontalAlignment="Left" Width="185" Click="AboutClick" />
|
||||
<MenuItem x:Name="CheckForUpdatesMenuItem" Header="_Check for Updates" HorizontalAlignment="Left" Width="185" Click="CheckForUpdatesClick" />
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<GroupBox Margin="5,5,5,5" HorizontalAlignment="Stretch" Header="Settings">
|
||||
<Grid Margin="5,5,5,5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="1*"/>
|
||||
<ColumnDefinition Width="2.5*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Content="System/Media Type" />
|
||||
<ComboBox x:Name="SystemTypeComboBox" Grid.Row="0" Grid.Column="1" Height="22" Width="250" HorizontalAlignment="Left">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=Name}" Foreground="{Binding Path=Foreground}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<ComboBox x:Name="MediaTypeComboBox" Grid.Row="0" Grid.Column="1" Height="22" Width="140" HorizontalAlignment="Right">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={StaticResource enumConverter}}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<Label Grid.Row="1" Grid.Column="0" VerticalAlignment="Center">Output Filename</Label>
|
||||
<TextBox x:Name="OutputFilenameTextBox" Grid.Row="1" Grid.Column="1" Height="22" />
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" VerticalAlignment="Center">Output Directory</Label>
|
||||
<TextBox x:Name="OutputDirectoryTextBox" Grid.Row="2" Grid.Column="1" Height="22" Width="345" HorizontalAlignment="Left" />
|
||||
<Button x:Name="OutputDirectoryBrowseButton" Grid.Row="2" Grid.Column="1" Height="22" Width="50" HorizontalAlignment="Right" Content="Browse" Click="OutputDirectoryBrowseButtonClick"/>
|
||||
|
||||
<Label Grid.Row="3" Grid.Column="0" VerticalAlignment="Center">Drive Letter</Label>
|
||||
<ComboBox x:Name="DriveLetterComboBox" Grid.Row="3" Grid.Column="1" Height="22" Width="60" HorizontalAlignment="Left">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=Letter}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<Label Grid.Row="4" Grid.Column="0" VerticalAlignment="Center">Drive Speed</Label>
|
||||
<ComboBox x:Name="DriveSpeedComboBox" Grid.Row="4" Grid.Column="1" Height="22" Width="60" HorizontalAlignment="Left" />
|
||||
|
||||
<Label Grid.Row="5" Grid.Column="0" VerticalAlignment="Center">Parameters</Label>
|
||||
<TextBox x:Name="ParametersTextBox" Grid.Row="5" Grid.Column="1" Height="22" Width="370" HorizontalAlignment="Left" IsEnabled="False" />
|
||||
<CheckBox x:Name="EnableParametersCheckBox" Grid.Row="5" Grid.Column="1" Height="22" HorizontalAlignment="Right" IsChecked="False" Click="EnableParametersCheckBoxClick" />
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Margin="5,5,5,5" HorizontalAlignment="Stretch" Header="Controls">
|
||||
<Grid Margin="5,5,5,5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="1*"/>
|
||||
<ColumnDefinition Width="1*"/>
|
||||
<ColumnDefinition Width="1*"/>
|
||||
<ColumnDefinition Width="1*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Button x:Name="StartStopButton" Grid.Row="0" Grid.Column="0" Height="22" Width="125" VerticalAlignment="Center" HorizontalAlignment="Center" Content="Start Dumping" Click="StartStopButtonClick" IsEnabled="False" />
|
||||
<Button x:Name="MediaScanButton" Grid.Row="0" Grid.Column="1" Height="22" Width="125" VerticalAlignment="Center" HorizontalAlignment="Center" Content="Scan for disks" Click="MediaScanButtonClick" />
|
||||
<Button x:Name="CopyProtectScanButton" Grid.Row="0" Grid.Column="2" Height="22" Width="125" VerticalAlignment="Center" HorizontalAlignment="Center" Content="Scan for protection" Click="CopyProtectScanButtonClick" />
|
||||
<CheckBox x:Name="EjectWhenDoneCheckBox" Grid.Row="0" Grid.Column="3" VerticalAlignment="Center" HorizontalAlignment="Center" Content="Eject When Done" IsChecked="False" />
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Margin="5,5,5,5" HorizontalAlignment="Stretch" Header="Status">
|
||||
<Grid Margin="5,5,5,5" Grid.ColumnSpan="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="1*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Label x:Name="StatusLabel" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Center" Content="Waiting for media..." />
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<Expander Margin="5,5 5 5" MaxHeight="300" HorizontalAlignment="Stretch" Header="Log Output" x:Name="LogPanel">
|
||||
<controls:LogOutput x:Name="LogOutput"/>
|
||||
</Expander>
|
||||
</StackPanel>
|
||||
<GroupBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Margin="5,5,5.2,5.4" HorizontalAlignment="Stretch" Header="Settings"/>
|
||||
<GroupBox Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Margin="5,4.6,5.2,4.8" HorizontalAlignment="Stretch" Header="Controls"/>
|
||||
<GroupBox Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" Margin="5,5.2,5.2,4.8" HorizontalAlignment="Stretch" Header="Status"/>
|
||||
|
||||
<Grid Grid.Row="1" Grid.Column="0" Margin="15,25,15.2,10.4" Grid.ColumnSpan="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="1*"/>
|
||||
<ColumnDefinition Width="2.5*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Content="System/Media Type" />
|
||||
<ComboBox x:Name="SystemTypeComboBox" Grid.Row="0" Grid.Column="1" Height="22" Width="250" HorizontalAlignment="Left">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=Name}" Foreground="{Binding Path=Foreground}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<ComboBox x:Name="MediaTypeComboBox" Grid.Row="0" Grid.Column="1" Height="22" Width="140" HorizontalAlignment="Right">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={StaticResource enumConverter}}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<Label Grid.Row="1" Grid.Column="0" VerticalAlignment="Center">Output Filename</Label>
|
||||
<TextBox x:Name="OutputFilenameTextBox" Grid.Row="1" Grid.Column="1" Height="22" />
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" VerticalAlignment="Center">Output Directory</Label>
|
||||
<TextBox x:Name="OutputDirectoryTextBox" Grid.Row="2" Grid.Column="1" Height="22" Width="345" HorizontalAlignment="Left" />
|
||||
<Button x:Name="OutputDirectoryBrowseButton" Grid.Row="2" Grid.Column="1" Height="22" Width="50" HorizontalAlignment="Right" Content="Browse" Click="OutputDirectoryBrowseButtonClick"/>
|
||||
|
||||
<Label Grid.Row="3" Grid.Column="0" VerticalAlignment="Center">Drive Letter</Label>
|
||||
<ComboBox x:Name="DriveLetterComboBox" Grid.Row="3" Grid.Column="1" Height="22" Width="60" HorizontalAlignment="Left">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=Letter}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<Label Grid.Row="4" Grid.Column="0" VerticalAlignment="Center">Drive Speed</Label>
|
||||
<ComboBox x:Name="DriveSpeedComboBox" Grid.Row="4" Grid.Column="1" Height="22" Width="60" HorizontalAlignment="Left" />
|
||||
|
||||
<Label Grid.Row="5" Grid.Column="0" VerticalAlignment="Center">Parameters</Label>
|
||||
<TextBox x:Name="ParametersTextBox" Grid.Row="5" Grid.Column="1" Height="22" Width="370" HorizontalAlignment="Left" IsEnabled="False" />
|
||||
<CheckBox x:Name="EnableParametersCheckBox" Grid.Row="5" Grid.Column="1" Height="22" HorizontalAlignment="Right" IsChecked="False" Click="EnableParametersCheckBoxClick" />
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="2" Grid.Column="0" Margin="15,19.6,15.2,9.8" Grid.ColumnSpan="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="1*"/>
|
||||
<ColumnDefinition Width="1*"/>
|
||||
<ColumnDefinition Width="1*"/>
|
||||
<ColumnDefinition Width="1*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Button x:Name="StartStopButton" Grid.Row="0" Grid.Column="0" Height="22" Width="125" VerticalAlignment="Center" HorizontalAlignment="Center" Content="Start Dumping" Click="StartStopButtonClick" IsEnabled="False" />
|
||||
<Button x:Name="MediaScanButton" Grid.Row="0" Grid.Column="1" Height="22" Width="125" VerticalAlignment="Center" HorizontalAlignment="Center" Content="Scan for disks" Click="MediaScanButtonClick" />
|
||||
<Button x:Name="CopyProtectScanButton" Grid.Row="0" Grid.Column="2" Height="22" Width="125" VerticalAlignment="Center" HorizontalAlignment="Center" Content="Scan for protection" Click="CopyProtectScanButtonClick" />
|
||||
<CheckBox x:Name="EjectWhenDoneCheckBox" Grid.Row="0" Grid.Column="3" VerticalAlignment="Center" HorizontalAlignment="Center" Content="Eject When Done" IsChecked="False" />
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="3" Grid.Column="0" Margin="15,20.2,15.2,9.8" Grid.ColumnSpan="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="1*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Label x:Name="StatusLabel" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Center" Content="Waiting for media..." />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
@@ -56,11 +56,6 @@ namespace MPF.Windows
|
||||
/// </summary>
|
||||
private bool alreadyShown;
|
||||
|
||||
/// <summary>
|
||||
/// Current attached LogWindow
|
||||
/// </summary>
|
||||
private readonly LogWindow logWindow;
|
||||
|
||||
#endregion
|
||||
|
||||
public MainWindow()
|
||||
@@ -70,24 +65,13 @@ namespace MPF.Windows
|
||||
// Load the options
|
||||
ViewModels.OptionsViewModel = new OptionsViewModel(UIOptions);
|
||||
|
||||
// Load the log window
|
||||
logWindow = new LogWindow(this);
|
||||
ViewModels.LoggerViewModel.SetWindow(logWindow);
|
||||
// Load the log output
|
||||
LogPanel.IsExpanded = UIOptions.Options.OpenLogWindowAtStartup;
|
||||
|
||||
// Disable buttons until we load fully
|
||||
StartStopButton.IsEnabled = false;
|
||||
MediaScanButton.IsEnabled = false;
|
||||
CopyProtectScanButton.IsEnabled = false;
|
||||
|
||||
if (UIOptions.Options.OpenLogWindowAtStartup)
|
||||
{
|
||||
this.WindowStartupLocation = WindowStartupLocation.Manual;
|
||||
double combinedHeight = this.Height + logWindow.Height + Constants.LogWindowMarginFromMainWindow;
|
||||
Rectangle bounds = GetScaledCoordinates(WinForms.Screen.PrimaryScreen.WorkingArea);
|
||||
|
||||
this.Left = bounds.Left + (bounds.Width - this.Width) / 2;
|
||||
this.Top = bounds.Top + (bounds.Height - combinedHeight) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
#region Helpers
|
||||
@@ -137,29 +121,29 @@ namespace MPF.Windows
|
||||
// If we're skipping detection, set the default value
|
||||
if (UIOptions.Options.SkipMediaTypeDetection)
|
||||
{
|
||||
ViewModels.LoggerViewModel.VerboseLogLn($"Media type detection disabled, defaulting to {defaultMediaType.LongName()}.");
|
||||
LogOutput.VerboseLogLn($"Media type detection disabled, defaulting to {defaultMediaType.LongName()}.");
|
||||
CurrentMediaType = defaultMediaType;
|
||||
}
|
||||
|
||||
// If the drive is marked active, try to read from it
|
||||
else if (drive.MarkedActive)
|
||||
{
|
||||
ViewModels.LoggerViewModel.VerboseLog($"Trying to detect media type for drive {drive.Letter}.. ");
|
||||
LogOutput.VerboseLog($"Trying to detect media type for drive {drive.Letter}.. ");
|
||||
(MediaType? detectedMediaType, string errorMessage) = Validators.GetMediaType(drive);
|
||||
|
||||
// If we got an error message, post it to the log
|
||||
if (errorMessage != null)
|
||||
ViewModels.LoggerViewModel.VerboseLogLn($"Error in detecting media type: {errorMessage}");
|
||||
LogOutput.VerboseLogLn($"Error in detecting media type: {errorMessage}");
|
||||
|
||||
// If we got either an error or no media, default to the current System default
|
||||
if (detectedMediaType == null)
|
||||
{
|
||||
ViewModels.LoggerViewModel.VerboseLogLn($"Unable to detect, defaulting to {defaultMediaType.LongName()}.");
|
||||
LogOutput.VerboseLogLn($"Unable to detect, defaulting to {defaultMediaType.LongName()}.");
|
||||
CurrentMediaType = defaultMediaType;
|
||||
}
|
||||
else
|
||||
{
|
||||
ViewModels.LoggerViewModel.VerboseLogLn($"Detected {CurrentMediaType.LongName()}.");
|
||||
LogOutput.VerboseLogLn($"Detected {CurrentMediaType.LongName()}.");
|
||||
CurrentMediaType = detectedMediaType;
|
||||
}
|
||||
}
|
||||
@@ -167,7 +151,7 @@ namespace MPF.Windows
|
||||
// All other cases, just use the default
|
||||
else
|
||||
{
|
||||
ViewModels.LoggerViewModel.VerboseLogLn($"Drive marked as empty, defaulting to {defaultMediaType.LongName()}.");
|
||||
LogOutput.VerboseLogLn($"Drive marked as empty, defaulting to {defaultMediaType.LongName()}.");
|
||||
CurrentMediaType = defaultMediaType;
|
||||
}
|
||||
}
|
||||
@@ -207,9 +191,9 @@ namespace MPF.Windows
|
||||
{
|
||||
if (!UIOptions.Options.SkipSystemDetection && DriveLetterComboBox.SelectedIndex > -1)
|
||||
{
|
||||
ViewModels.LoggerViewModel.VerboseLog($"Trying to detect system for drive {Drives[DriveLetterComboBox.SelectedIndex].Letter}.. ");
|
||||
LogOutput.VerboseLog($"Trying to detect system for drive {Drives[DriveLetterComboBox.SelectedIndex].Letter}.. ");
|
||||
var currentSystem = Validators.GetKnownSystem(Drives[DriveLetterComboBox.SelectedIndex]) ?? Converters.ToKnownSystem(UIOptions.Options.DefaultSystem);
|
||||
ViewModels.LoggerViewModel.VerboseLogLn(currentSystem == null || currentSystem == KnownSystem.NONE ? "unable to detect." : ("detected " + currentSystem.LongName() + "."));
|
||||
LogOutput.VerboseLogLn(currentSystem == null || currentSystem == KnownSystem.NONE ? "unable to detect." : ("detected " + currentSystem.LongName() + "."));
|
||||
|
||||
if (currentSystem != null && currentSystem != KnownSystem.NONE)
|
||||
{
|
||||
@@ -286,7 +270,7 @@ namespace MPF.Windows
|
||||
/// <remarks>TODO: Find a way for this to periodically run, or have it hook to a "drive change" event</remarks>
|
||||
private void PopulateDrives()
|
||||
{
|
||||
ViewModels.LoggerViewModel.VerboseLogLn("Scanning for drives..");
|
||||
LogOutput.VerboseLogLn("Scanning for drives..");
|
||||
|
||||
// Always enable the media scan
|
||||
MediaScanButton.IsEnabled = true;
|
||||
@@ -297,7 +281,7 @@ namespace MPF.Windows
|
||||
|
||||
if (DriveLetterComboBox.Items.Count > 0)
|
||||
{
|
||||
ViewModels.LoggerViewModel.VerboseLogLn($"Found {Drives.Count} drives: {string.Join(", ", Drives.Select(d => d.Letter))}");
|
||||
LogOutput.VerboseLogLn($"Found {Drives.Count} drives: {string.Join(", ", Drives.Select(d => d.Letter))}");
|
||||
|
||||
// Check for active optical drives first
|
||||
int index = Drives.FindIndex(d => d.MarkedActive && d.InternalDriveType == InternalDriveType.Optical);
|
||||
@@ -324,7 +308,7 @@ namespace MPF.Windows
|
||||
}
|
||||
else
|
||||
{
|
||||
ViewModels.LoggerViewModel.VerboseLogLn("Found no drives");
|
||||
LogOutput.VerboseLogLn("Found no drives");
|
||||
DriveLetterComboBox.SelectedIndex = -1;
|
||||
StatusLabel.Content = "No valid drive found!";
|
||||
StartStopButton.IsEnabled = false;
|
||||
@@ -362,7 +346,7 @@ namespace MPF.Windows
|
||||
{
|
||||
var knownSystems = Validators.CreateListOfSystems();
|
||||
|
||||
ViewModels.LoggerViewModel.VerboseLogLn($"Populating systems, {knownSystems.Count} systems found.");
|
||||
LogOutput.VerboseLogLn($"Populating systems, {knownSystems.Count} systems found.");
|
||||
|
||||
Dictionary<KnownSystemCategory, List<KnownSystem?>> mapping = knownSystems
|
||||
.GroupBy(s => s.Category())
|
||||
@@ -496,7 +480,7 @@ namespace MPF.Windows
|
||||
var drive = DriveLetterComboBox.SelectedItem as Drive;
|
||||
if (drive.Letter != default(char))
|
||||
{
|
||||
ViewModels.LoggerViewModel.VerboseLogLn($"Scanning for copy protection in {drive.Letter}");
|
||||
LogOutput.VerboseLogLn($"Scanning for copy protection in {drive.Letter}");
|
||||
|
||||
var tempContent = StatusLabel.Content;
|
||||
StatusLabel.Content = "Scanning for copy protection... this might take a while!";
|
||||
@@ -512,13 +496,13 @@ namespace MPF.Windows
|
||||
if (Env.InternalProgram == InternalProgram.DiscImageCreator && protections.Contains("SmartE"))
|
||||
{
|
||||
((DiscImageCreator.Parameters)Env.Parameters)[DiscImageCreator.Flag.ScanFileProtect] = false;
|
||||
ViewModels.LoggerViewModel.VerboseLogLn($"SmartE detected, removing {DiscImageCreator.FlagStrings.ScanFileProtect} from parameters");
|
||||
LogOutput.VerboseLogLn($"SmartE detected, removing {DiscImageCreator.FlagStrings.ScanFileProtect} from parameters");
|
||||
}
|
||||
|
||||
if (!ViewModels.LoggerViewModel.WindowVisible)
|
||||
if (!LogPanel.IsExpanded)
|
||||
MessageBox.Show(protections, "Detected Protection", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
|
||||
ViewModels.LoggerViewModel.VerboseLog($"Detected the following protections in {drive.Letter}:\r\n\r\n{protections}");
|
||||
LogOutput.VerboseLog($"Detected the following protections in {drive.Letter}:\r\n\r\n{protections}");
|
||||
|
||||
StatusLabel.Content = tempContent;
|
||||
StartStopButton.IsEnabled = true;
|
||||
@@ -552,11 +536,11 @@ namespace MPF.Windows
|
||||
// Set the drive speed list that's appropriate
|
||||
var values = Interface.GetSpeedsForMediaType(CurrentMediaType);
|
||||
DriveSpeedComboBox.ItemsSource = values;
|
||||
ViewModels.LoggerViewModel.VerboseLogLn($"Supported media speeds: {string.Join(", ", values)}");
|
||||
LogOutput.VerboseLogLn($"Supported media speeds: {string.Join(", ", values)}");
|
||||
|
||||
// Set the selected speed
|
||||
int speed = UIOptions.GetPreferredDumpSpeedForMediaType(CurrentMediaType);
|
||||
ViewModels.LoggerViewModel.VerboseLogLn($"Setting drive speed to: {speed}");
|
||||
LogOutput.VerboseLogLn($"Setting drive speed to: {speed}");
|
||||
DriveSpeedComboBox.SelectedValue = speed;
|
||||
}
|
||||
|
||||
@@ -608,7 +592,7 @@ namespace MPF.Windows
|
||||
MessageBoxResult mbresult = MessageBox.Show("The currently selected drive does not appear to contain a disc! Are you sure you want to continue?", "Missing Disc", MessageBoxButton.YesNo, MessageBoxImage.Exclamation);
|
||||
if (mbresult == MessageBoxResult.No || mbresult == MessageBoxResult.Cancel || mbresult == MessageBoxResult.None)
|
||||
{
|
||||
ViewModels.LoggerViewModel.VerboseLogLn("Dumping aborted!");
|
||||
LogOutput.VerboseLogLn("Dumping aborted!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -619,7 +603,7 @@ namespace MPF.Windows
|
||||
MessageBoxResult mbresult = MessageBox.Show("A complete dump already exists! Are you sure you want to overwrite?", "Overwrite?", MessageBoxButton.YesNo, MessageBoxImage.Exclamation);
|
||||
if (mbresult == MessageBoxResult.No || mbresult == MessageBoxResult.Cancel || mbresult == MessageBoxResult.None)
|
||||
{
|
||||
ViewModels.LoggerViewModel.VerboseLogLn("Dumping aborted!");
|
||||
LogOutput.VerboseLogLn("Dumping aborted!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -627,7 +611,7 @@ namespace MPF.Windows
|
||||
StartStopButton.Content = Interface.StopDumping;
|
||||
CopyProtectScanButton.IsEnabled = false;
|
||||
StatusLabel.Content = "Beginning dumping process";
|
||||
ViewModels.LoggerViewModel.VerboseLogLn("Starting dumping process..");
|
||||
LogOutput.VerboseLogLn("Starting dumping process..");
|
||||
|
||||
// Get progress indicators
|
||||
var resultProgress = new Progress<Result>();
|
||||
@@ -641,7 +625,7 @@ namespace MPF.Windows
|
||||
// If we didn't execute a dumping command we cannot get submission output
|
||||
if (!Env.Parameters.IsDumpingCommand())
|
||||
{
|
||||
ViewModels.LoggerViewModel.VerboseLogLn("No dumping command was run, submission information will not be gathered.");
|
||||
LogOutput.VerboseLogLn("No dumping command was run, submission information will not be gathered.");
|
||||
StatusLabel.Content = "Execution complete!";
|
||||
StartStopButton.Content = Interface.StartDumping;
|
||||
CopyProtectScanButton.IsEnabled = true;
|
||||
@@ -660,13 +644,13 @@ namespace MPF.Windows
|
||||
}
|
||||
else
|
||||
{
|
||||
ViewModels.LoggerViewModel.VerboseLogLn(result.Message);
|
||||
LogOutput.VerboseLogLn(result.Message);
|
||||
StatusLabel.Content = "Execution failed!";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ViewModels.LoggerViewModel.VerboseLogLn(ex.Message);
|
||||
LogOutput.VerboseLogLn(ex.Message);
|
||||
StatusLabel.Content = "An exception occurred!";
|
||||
}
|
||||
finally
|
||||
@@ -775,36 +759,6 @@ namespace MPF.Windows
|
||||
InitializeUIValues(removeEventHandlers: true, rescanDrives: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler for MainWindow Activated event
|
||||
/// </summary>
|
||||
private void MainWindowActivated(object sender, EventArgs e)
|
||||
{
|
||||
if (logWindow.IsVisible && !this.Topmost)
|
||||
{
|
||||
logWindow.Topmost = true;
|
||||
logWindow.Topmost = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler for MainWindow Closing event
|
||||
/// </summary>
|
||||
private void MainWindowClosing(object sender, System.ComponentModel.CancelEventArgs e)
|
||||
{
|
||||
if (logWindow.IsVisible)
|
||||
logWindow.Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler for MainWindow PositionChanged event
|
||||
/// </summary>
|
||||
private void MainWindowLocationChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (logWindow.IsVisible)
|
||||
logWindow.AdjustPositionToMainWindow();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler for MainWindow OnContentRendered event
|
||||
/// </summary>
|
||||
@@ -818,14 +772,6 @@ namespace MPF.Windows
|
||||
|
||||
alreadyShown = true;
|
||||
|
||||
if (UIOptions.Options.OpenLogWindowAtStartup)
|
||||
{
|
||||
//TODO: this should be bound directly to WindowVisible property in two way fashion
|
||||
// we need to study how to properly do it in XAML
|
||||
ShowLogMenuItem.IsChecked = true;
|
||||
ViewModels.LoggerViewModel.WindowVisible = true;
|
||||
}
|
||||
|
||||
// Populate the list of systems
|
||||
StatusLabel.Content = "Creating system list, please wait!";
|
||||
PopulateSystems();
|
||||
@@ -839,7 +785,7 @@ namespace MPF.Windows
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler for OptionsWindow OnUpdated event
|
||||
/// Handler for DiscInformationWindow OnUpdated event
|
||||
/// </summary>
|
||||
/// <remarks>Identical to MediaScanButtonClick</remarks>
|
||||
public void OnOptionsUpdated()
|
||||
@@ -892,7 +838,7 @@ namespace MPF.Windows
|
||||
private void ProgressUpdated(object sender, Result value)
|
||||
{
|
||||
StatusLabel.Content = value.Message;
|
||||
ViewModels.LoggerViewModel.VerboseLogLn(value.Message);
|
||||
LogOutput.VerboseLogLn(value.Message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -902,7 +848,7 @@ namespace MPF.Windows
|
||||
{
|
||||
string message = $"{value.Percentage * 100:N2}%: {value.Filename} - {value.Protection}";
|
||||
StatusLabel.Content = message;
|
||||
ViewModels.LoggerViewModel.VerboseLogLn(message);
|
||||
LogOutput.VerboseLogLn(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -917,19 +863,19 @@ namespace MPF.Windows
|
||||
}
|
||||
else if ((string)StartStopButton.Content == Interface.StopDumping)
|
||||
{
|
||||
ViewModels.LoggerViewModel.VerboseLogLn("Canceling dumping process...");
|
||||
LogOutput.VerboseLogLn("Canceling dumping process...");
|
||||
Env.CancelDumping();
|
||||
CopyProtectScanButton.IsEnabled = true;
|
||||
|
||||
if (EjectWhenDoneCheckBox.IsChecked == true)
|
||||
{
|
||||
ViewModels.LoggerViewModel.VerboseLogLn($"Ejecting disc in drive {Env.Drive.Letter}");
|
||||
LogOutput.VerboseLogLn($"Ejecting disc in drive {Env.Drive.Letter}");
|
||||
Env.EjectDisc();
|
||||
}
|
||||
|
||||
if (UIOptions.Options.ResetDriveAfterDump)
|
||||
{
|
||||
ViewModels.LoggerViewModel.VerboseLogLn($"Resetting drive {Env.Drive.Letter}");
|
||||
LogOutput.VerboseLogLn($"Resetting drive {Env.Drive.Letter}");
|
||||
Env.ResetDrive();
|
||||
}
|
||||
}
|
||||
@@ -947,44 +893,12 @@ namespace MPF.Windows
|
||||
return;
|
||||
}
|
||||
|
||||
ViewModels.LoggerViewModel.VerboseLogLn($"Changed system to: {(SystemTypeComboBox.SelectedItem as KnownSystemComboBoxItem).Name}");
|
||||
LogOutput.VerboseLogLn($"Changed system to: {(SystemTypeComboBox.SelectedItem as KnownSystemComboBoxItem).Name}");
|
||||
PopulateMediaType();
|
||||
GetOutputNames(false);
|
||||
EnsureDiscInformation();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UI Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Get pixel coordinates based on DPI scaling
|
||||
/// </summary>
|
||||
/// <param name="bounds">Rectangle representing the bounds to transform</param>
|
||||
/// <returns>Rectangle representing the scaled bounds</returns>
|
||||
private Rectangle GetScaledCoordinates(Rectangle bounds)
|
||||
{
|
||||
using (Graphics g = Graphics.FromHwnd(IntPtr.Zero))
|
||||
{
|
||||
return new Rectangle(
|
||||
TransformCoordinate(bounds.Left, g.DpiX),
|
||||
TransformCoordinate(bounds.Top, g.DpiY),
|
||||
TransformCoordinate(bounds.Width, g.DpiX),
|
||||
TransformCoordinate(bounds.Height, g.DpiY));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transform an individual coordinate using DPI scaling
|
||||
/// </summary>
|
||||
/// <param name="coord">Current integer coordinate</param>
|
||||
/// <param name="dpi">DPI scaling factor</param>
|
||||
/// <returns>Scaled integer coordinate</returns>
|
||||
private int TransformCoordinate(int coord, float dpi)
|
||||
{
|
||||
return (int)(coord / ((double)dpi / 96));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,8 +64,6 @@ namespace MPF.Windows
|
||||
{
|
||||
// We only support certain programs for dumping
|
||||
var internalPrograms = new List<InternalProgram> { InternalProgram.DiscImageCreator, InternalProgram.Aaru, InternalProgram.DD };
|
||||
ViewModels.LoggerViewModel.VerboseLogLn($"Populating internal programs, {internalPrograms.Count} internal programs found.");
|
||||
|
||||
InternalPrograms = new List<InternalProgramComboBoxItem>();
|
||||
foreach (var internalProgram in internalPrograms)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user