[TUI] Migrate to Prism.Avalonia.

This commit is contained in:
2025-11-18 04:36:49 +00:00
parent 31dfcadab8
commit b580bf0555
17 changed files with 901 additions and 755 deletions

View File

@@ -13,6 +13,8 @@
<ItemGroup>
<PackageReference Include="Consolonia"/>
<PackageReference Include="Prism.Avalonia"/>
<PackageReference Include="Prism.DryIoc.Avalonia"/>
</ItemGroup>
<ItemGroup>

View File

@@ -2,8 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:console="https://github.com/jinek/consolonia"
x:Class="Aaru.Tui.App"
RequestedThemeVariant="Dark">
RequestedThemeVariant="Light">
<Application.Styles>
<console:TurboVisionGrayTheme />
<console:TurboVisionTheme />
</Application.Styles>
</Application>

View File

@@ -28,12 +28,50 @@
using Aaru.Tui.ViewModels.Windows;
using Aaru.Tui.Views.Windows;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Prism.DryIoc;
namespace Aaru.Tui;
public class App : Application
public class App : PrismApplication
{
/// <inheritdoc />
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
base.Initialize();
}
/// <inheritdoc />
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
// Register your Services, Views, Dialogs, etc. here
// Views - Region Navigation
containerRegistry.RegisterForNavigation<FileView, FileViewViewModel>();
containerRegistry.RegisterForNavigation<HexViewWindow, HexViewWindowViewModel>();
containerRegistry.RegisterForNavigation<ImageWindow, ImageWindowViewModel>();
}
/// <inheritdoc />
protected override AvaloniaObject CreateShell() => Container.Resolve<MainWindow>();
/// <summary>Called after Initialize.</summary>
protected override void OnInitialized()
{
// Register Views to the Region it will appear in. Don't register them in the ViewModel.
IRegionManager regionManager = Container.Resolve<IRegionManager>();
// WARNING: Prism v11.0.0
// - DataTemplates MUST define a DataType or else an XAML error will be thrown
// - Error: DataTemplate inside of DataTemplates must have a DataType set
regionManager.RegisterViewWithRegion("ContentRegion", typeof(FileView));
}
}
/*public class App : Application
{
public override void Initialize()
{
@@ -52,4 +90,4 @@ public class App : Application
base.OnFrameworkInitializationCompleted();
}
}
}*/

View File

@@ -29,4 +29,30 @@ using CommunityToolkit.Mvvm.ComponentModel;
namespace Aaru.Tui.ViewModels;
public class ViewModelBase : ObservableObject;
public class ViewModelBase : ObservableObject, IRegionAware
{
/// <summary>
/// Called to determine if this instance can handle the navigation request.
/// Don't call this directly, use <seealso cref="OnNavigatingTo(NavigationContext)" />.
/// </summary>
/// <param name="navigationContext">The navigation context.</param>
/// <returns><see langword="true" /> if this instance accepts the navigation request; otherwise, <see langword="false" />.</returns>
public virtual bool IsNavigationTarget(NavigationContext navigationContext) =>
// Auto-allow navigation
OnNavigatingTo(navigationContext);
/// <summary>Called when the implementer is being navigated away from.</summary>
/// <param name="navigationContext">The navigation context.</param>
public virtual void OnNavigatedFrom(NavigationContext navigationContext) {}
/// <summary>Called when the implementer has been navigated to.</summary>
/// <param name="navigationContext">The navigation context.</param>
public virtual void OnNavigatedTo(NavigationContext navigationContext) {}
/// <summary>Navigation validation checker.</summary>
/// <remarks>Override for Prism 7.2's IsNavigationTarget.</remarks>
/// <param name="navigationContext">The navigation context.</param>
/// <returns><see langword="true" /> if this instance accepts the navigation request; otherwise, <see langword="false" />.</returns>
public virtual bool OnNavigatingTo(NavigationContext navigationContext) => true;
}

View File

@@ -0,0 +1,479 @@
using System.Collections.ObjectModel;
using System.Reflection;
using System.Text;
using System.Windows.Input;
using Aaru.CommonTypes;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
using Aaru.Core;
using Aaru.Helpers;
using Aaru.Tui.Models;
using Aaru.Tui.Views.Windows;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Humanizer;
using Humanizer.Bytes;
using Spectre.Console;
using Color = Avalonia.Media.Color;
namespace Aaru.Tui.ViewModels.Windows;
public sealed partial class FileViewViewModel : ViewModelBase
{
readonly IRegionManager _regionManager;
[ObservableProperty]
string _copyright;
[ObservableProperty]
string _currentPath;
[ObservableProperty]
ObservableCollection<FileModel> _files = [];
[ObservableProperty]
string _informationalVersion;
[ObservableProperty]
bool _isStatusVisible;
FileModel? _selectedFile;
[ObservableProperty]
string _status;
public FileViewViewModel(IRegionManager regionManager)
{
ExitCommand = new RelayCommand(Exit);
SectorViewCommand = new RelayCommand(SectorView);
GoToPathCommand = new AsyncRelayCommand(GoToPathAsync);
HelpCommand = new AsyncRelayCommand(HelpAsync);
OpenSelectedFileCommand = new RelayCommand(OpenSelectedFile, CanOpenSelectedFile);
_regionManager = regionManager;
InformationalVersion =
Assembly.GetExecutingAssembly()
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
?.InformationalVersion ??
"?.?.?";
Copyright = Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyCopyrightAttribute>()?.Copyright ?? "";
Status = Localization.Resources.Loading;
}
public FileModel? SelectedFile
{
get => _selectedFile;
set
{
SetProperty(ref _selectedFile, value);
OnPropertyChanged(nameof(IsFileInfoAvailable));
OnPropertyChanged(nameof(SelectedFileIsNotDirectory));
OnPropertyChanged(nameof(SelectedFileLength));
OnPropertyChanged(nameof(SelectedFileCreationTime));
OnPropertyChanged(nameof(SelectedFileLastWriteTime));
OnPropertyChanged(nameof(SelectedFileAttributes));
OnPropertyChanged(nameof(SelectedFileUnixMode));
OnPropertyChanged(nameof(SelectedFileHasInformation));
OnPropertyChanged(nameof(SelectedFileInformation));
}
}
public ICommand OpenSelectedFileCommand { get; }
public ICommand ExitCommand { get; }
public ICommand SectorViewCommand { get; }
public ICommand GoToPathCommand { get; }
public ICommand HelpCommand { get; }
public bool IsFileInfoAvailable => SelectedFile?.FileInfo != null;
public bool SelectedFileIsNotDirectory => SelectedFile?.IsDirectory == false;
public long? SelectedFileLength => SelectedFile?.IsDirectory == false ? SelectedFile?.FileInfo?.Length : 0;
public DateTime? SelectedFileCreationTime => SelectedFile?.FileInfo?.CreationTime;
public DateTime? SelectedFileLastWriteTime => SelectedFile?.FileInfo?.LastWriteTime;
public string? SelectedFileAttributes => SelectedFile?.FileInfo?.Attributes.ToString();
public string? SelectedFileUnixMode => SelectedFile?.FileInfo?.UnixFileMode.ToString();
public bool SelectedFileHasInformation => SelectedFile?.Information != null;
public string? SelectedFileInformation => SelectedFile?.Information;
Task HelpAsync() =>
/* var dialog = new MainHelpDialog
{
DataContext = new MainHelpDialogViewModel(null!)
};
// Set the dialog reference after creation
((MainHelpDialogViewModel)dialog.DataContext!)._dialog = dialog;
return dialog.ShowDialog(_view);*/
Task.CompletedTask;
Task GoToPathAsync() =>
/* var dialog = new GoToPathDialog
{
DataContext = new GoToPathDialogViewModel(null!)
};
// Set the dialog reference after creation
((GoToPathDialogViewModel)dialog.DataContext!)._dialog = dialog;
bool? result = await dialog.ShowDialog<bool?>(_view);
if(result == true)
{
var viewModel = (GoToPathDialogViewModel)dialog.DataContext;
if(viewModel.Path is not null && Directory.Exists(viewModel.Path))
{
Environment.CurrentDirectory = viewModel.Path;
LoadFiles();
}
}*/
Task.CompletedTask;
void SectorView()
{
if(SelectedFile?.ImageFormat is null) return;
var parameters = new NavigationParameters
{
{
"imageFormat", SelectedFile.ImageFormat
},
{
"filePath", SelectedFile.Path
}
};
_regionManager.RequestNavigate("ContentRegion", nameof(HexViewWindow), parameters);
}
void Exit()
{
var lifetime = Application.Current!.ApplicationLifetime as IControlledApplicationLifetime;
lifetime!.Shutdown();
}
/// <inheritdoc />
public override void OnNavigatedTo(NavigationContext navigationContext)
{
LoadFiles();
}
public void LoadComplete()
{
LoadFiles();
}
public void LoadFiles()
{
IsStatusVisible = true;
Status = Localization.Resources.Loading;
CurrentPath = Directory.GetCurrentDirectory();
Files.Clear();
var parentDirectory = new FileModel
{
Filename = "..",
Path = Path.GetRelativePath(CurrentPath, ".."),
ForegroundBrush =
new SolidColorBrush(Color.Parse(DirColorsParser.Instance.DirectoryColor ??
DirColorsParser.Instance.NormalColor)),
IsDirectory = true
};
Files.Add(parentDirectory);
foreach(FileModel model in Directory.GetDirectories(CurrentPath, "*", SearchOption.TopDirectoryOnly)
.Select(directory => new FileModel
{
Path = directory,
Filename = Path.GetFileName(directory),
ForegroundBrush =
new SolidColorBrush(Color.Parse(DirColorsParser.Instance
.DirectoryColor ??
DirColorsParser.Instance.NormalColor)),
IsDirectory = true
}))
Files.Add(model);
foreach(string file in Directory.GetFiles(CurrentPath, "*", SearchOption.TopDirectoryOnly))
{
var model = new FileModel
{
Path = file,
Filename = Path.GetFileName(file)
};
string extension = Path.GetExtension(file);
model.ForegroundBrush =
new SolidColorBrush(Color.Parse(DirColorsParser.Instance.ExtensionColors.TryGetValue(extension,
out string? hex)
? hex
: DirColorsParser.Instance.NormalColor));
Files.Add(model);
}
_ = Task.Run(Worker);
}
void Worker()
{
IsStatusVisible = true;
Status = Localization.Resources.Loading_file_information;
foreach(FileModel file in Files)
{
try
{
file.FileInfo = new FileInfo(file.Path);
IFilter inputFilter = PluginRegister.Singleton.GetFilter(file.Path);
if(inputFilter is null) continue;
IBaseImage imageFormat = ImageFormat.Detect(inputFilter);
if(imageFormat is null) continue;
ErrorNumber opened = imageFormat.Open(inputFilter);
if(opened != ErrorNumber.NoError) continue;
StringBuilder sb = new();
if(!string.IsNullOrWhiteSpace(imageFormat.Info.Version))
{
sb.AppendFormat(Aaru.Localization.Core.Format_0_version_1_WithMarkup,
Markup.Escape(imageFormat.Format),
Markup.Escape(imageFormat.Info.Version))
.AppendLine();
}
else
{
sb.AppendFormat(Aaru.Localization.Core.Format_0_WithMarkup, Markup.Escape(imageFormat.Format))
.AppendLine();
}
switch(string.IsNullOrWhiteSpace(imageFormat.Info.Application))
{
case false when !string.IsNullOrWhiteSpace(imageFormat.Info.ApplicationVersion):
sb.AppendFormat(Aaru.Localization.Core.Was_created_with_0_version_1_WithMarkup,
Markup.Escape(imageFormat.Info.Application),
Markup.Escape(imageFormat.Info.ApplicationVersion))
.AppendLine();
break;
case false:
sb.AppendFormat(Aaru.Localization.Core.Was_created_with_0_WithMarkup,
Markup.Escape(imageFormat.Info.Application))
.AppendLine();
break;
}
sb.AppendFormat(Aaru.Localization.Core.Image_without_headers_is_0_bytes_long,
imageFormat.Info.ImageSize)
.AppendLine();
sb.AppendFormat(Aaru.Localization.Core.Contains_a_media_of_0_sectors, imageFormat.Info.Sectors)
.AppendLine();
sb.AppendFormat(Aaru.Localization.Core.Maximum_sector_size_of_0_bytes, imageFormat.Info.SectorSize)
.AppendLine();
sb.AppendFormat(Aaru.Localization.Core.Would_be_0_humanized,
ByteSize.FromBytes(imageFormat.Info.Sectors * imageFormat.Info.SectorSize).Humanize())
.AppendLine();
if(!string.IsNullOrWhiteSpace(imageFormat.Info.Creator))
{
sb.AppendFormat(Aaru.Localization.Core.Created_by_0_WithMarkup,
Markup.Escape(imageFormat.Info.Creator))
.AppendLine();
}
if(imageFormat.Info.CreationTime != DateTime.MinValue)
sb.AppendFormat(Aaru.Localization.Core.Created_on_0, imageFormat.Info.CreationTime).AppendLine();
if(imageFormat.Info.LastModificationTime != DateTime.MinValue)
{
sb.AppendFormat(Aaru.Localization.Core.Last_modified_on_0, imageFormat.Info.LastModificationTime)
.AppendLine();
}
sb.AppendFormat(Aaru.Localization.Core.Contains_a_media_of_type_0,
imageFormat.Info.MediaType.Humanize())
.AppendLine();
sb.AppendFormat(Aaru.Localization.Core.XML_type_0, imageFormat.Info.MetadataMediaType).AppendLine();
sb.AppendLine(imageFormat.Info.HasPartitions
? Aaru.Localization.Core.Has_partitions
: Aaru.Localization.Core.Doesnt_have_partitions);
sb.AppendLine(imageFormat.Info.HasSessions
? Aaru.Localization.Core.Has_sessions
: Aaru.Localization.Core.Doesnt_have_sessions);
if(!string.IsNullOrWhiteSpace(imageFormat.Info.Comments))
{
sb.AppendFormat(Aaru.Localization.Core.Comments_0_WithMarkup,
Markup.Escape(imageFormat.Info.Comments))
.AppendLine();
}
if(imageFormat.Info.MediaSequence != 0 && imageFormat.Info.LastMediaSequence != 0)
{
sb.AppendFormat(Aaru.Localization.Core.Media_is_number_0_on_a_set_of_1_medias,
imageFormat.Info.MediaSequence,
imageFormat.Info.LastMediaSequence)
.AppendLine();
}
if(!string.IsNullOrWhiteSpace(imageFormat.Info.MediaTitle))
{
sb.AppendFormat(Aaru.Localization.Core.Media_title_0_WithMarkup,
Markup.Escape(imageFormat.Info.MediaTitle))
.AppendLine();
}
if(!string.IsNullOrWhiteSpace(imageFormat.Info.MediaManufacturer))
{
sb.AppendFormat(Aaru.Localization.Core.Media_manufacturer_0_WithMarkup,
Markup.Escape(imageFormat.Info.MediaManufacturer))
.AppendLine();
}
if(!string.IsNullOrWhiteSpace(imageFormat.Info.MediaModel))
{
sb.AppendFormat(Aaru.Localization.Core.Media_model_0_WithMarkup,
Markup.Escape(imageFormat.Info.MediaModel))
.AppendLine();
}
if(!string.IsNullOrWhiteSpace(imageFormat.Info.MediaSerialNumber))
{
sb.AppendFormat(Aaru.Localization.Core.Media_serial_number_0_WithMarkup,
Markup.Escape(imageFormat.Info.MediaSerialNumber))
.AppendLine();
}
if(!string.IsNullOrWhiteSpace(imageFormat.Info.MediaBarcode))
{
sb.AppendFormat(Aaru.Localization.Core.Media_barcode_0_WithMarkup,
Markup.Escape(imageFormat.Info.MediaBarcode))
.AppendLine();
}
if(!string.IsNullOrWhiteSpace(imageFormat.Info.MediaPartNumber))
{
sb.AppendFormat(Aaru.Localization.Core.Media_part_number_0_WithMarkup,
Markup.Escape(imageFormat.Info.MediaPartNumber))
.AppendLine();
}
if(!string.IsNullOrWhiteSpace(imageFormat.Info.DriveManufacturer))
{
sb.AppendFormat(Aaru.Localization.Core.Drive_manufacturer_0_WithMarkup,
Markup.Escape(imageFormat.Info.DriveManufacturer))
.AppendLine();
}
if(!string.IsNullOrWhiteSpace(imageFormat.Info.DriveModel))
{
sb.AppendFormat(Aaru.Localization.Core.Drive_model_0_WithMarkup,
Markup.Escape(imageFormat.Info.DriveModel))
.AppendLine();
}
if(!string.IsNullOrWhiteSpace(imageFormat.Info.DriveSerialNumber))
{
sb.AppendFormat(Aaru.Localization.Core.Drive_serial_number_0_WithMarkup,
Markup.Escape(imageFormat.Info.DriveSerialNumber))
.AppendLine();
}
if(!string.IsNullOrWhiteSpace(imageFormat.Info.DriveFirmwareRevision))
{
sb.AppendFormat(Aaru.Localization.Core.Drive_firmware_info_0_WithMarkup,
Markup.Escape(imageFormat.Info.DriveFirmwareRevision))
.AppendLine();
}
if(imageFormat.Info.Cylinders > 0 &&
imageFormat.Info is { Heads: > 0, SectorsPerTrack: > 0 } &&
imageFormat.Info.MetadataMediaType != MetadataMediaType.OpticalDisc &&
imageFormat is not ITapeImage { IsTape: true })
{
sb.AppendFormat(Aaru.Localization.Core
.Media_geometry_0_cylinders_1_heads_2_sectors_per_track_WithMarkup,
imageFormat.Info.Cylinders,
imageFormat.Info.Heads,
imageFormat.Info.SectorsPerTrack)
.AppendLine();
}
if(imageFormat.Info.ReadableMediaTags is { Count: > 0 })
{
sb.AppendFormat(Aaru.Localization.Core.Contains_0_readable_media_tags_WithMarkup,
imageFormat.Info.ReadableMediaTags.Count)
.AppendLine();
foreach(MediaTagType tag in imageFormat.Info.ReadableMediaTags.Order())
sb.Append($"[italic][rosybrown]{Markup.Escape(tag.ToString())}[/][/] ");
sb.AppendLine();
}
if(imageFormat.Info.ReadableSectorTags is { Count: > 0 })
{
sb.AppendFormat(Aaru.Localization.Core.Contains_0_readable_sector_tags_WithMarkup,
imageFormat.Info.ReadableSectorTags.Count)
.AppendLine();
foreach(SectorTagType tag in imageFormat.Info.ReadableSectorTags.Order())
sb.Append($"[italic][rosybrown]{Markup.Escape(tag.ToString())}[/][/] ");
sb.AppendLine();
}
file.Information = sb.ToString();
file.ImageFormat = imageFormat as IMediaImage;
}
catch(Exception ex)
{
SentrySdk.CaptureException(ex);
}
}
Status = Localization.Resources.Done;
IsStatusVisible = false;
}
void OpenSelectedFile()
{
if(SelectedFile.IsDirectory)
{
CurrentPath = SelectedFile.Path;
Environment.CurrentDirectory = CurrentPath;
LoadFiles();
return;
}
if(SelectedFile.ImageFormat is null) return;
var parameters = new NavigationParameters
{
{
"imageFormat", SelectedFile.ImageFormat
},
{
"filePath", SelectedFile.Path
}
};
_regionManager.RequestNavigate("ContentRegion", nameof(ImageWindow), parameters);
}
bool CanOpenSelectedFile() => SelectedFile != null;
}

View File

@@ -31,6 +31,7 @@ using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
using Aaru.Tui.ViewModels.Dialogs;
using Aaru.Tui.Views.Dialogs;
using Aaru.Tui.Views.Windows;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using CommunityToolkit.Mvvm.ComponentModel;
@@ -43,7 +44,7 @@ namespace Aaru.Tui.ViewModels.Windows;
public sealed partial class HexViewWindowViewModel : ViewModelBase
{
private const int BYTES_PER_LINE = 16;
readonly IMediaImage _imageFormat;
readonly IRegionManager _regionManager;
readonly ManagedWindow _view;
[ObservableProperty]
ulong _currentSector;
@@ -51,13 +52,14 @@ public sealed partial class HexViewWindowViewModel : ViewModelBase
string _filePath;
[ObservableProperty]
long _fileSize;
IMediaImage _imageFormat;
[ObservableProperty]
ObservableCollection<HexViewLine> _lines = [];
bool _longMode;
internal HexViewWindowViewModel(ManagedWindow view, IMediaImage imageFormat, string filePath,
ulong currentSector = 0)
internal HexViewWindowViewModel(IRegionManager regionManager)
{
_regionManager = regionManager;
ExitCommand = new RelayCommand(Exit);
BackCommand = new RelayCommand(Back);
NextSectorCommand = new RelayCommand(NextSector);
@@ -65,12 +67,6 @@ public sealed partial class HexViewWindowViewModel : ViewModelBase
GoToCommand = new AsyncRelayCommand(GoToAsync);
HelpCommand = new AsyncRelayCommand(HelpAsync);
ToggleLongCommand = new RelayCommand(ToggleLong);
_view = view;
_imageFormat = imageFormat;
_currentSector = currentSector;
FilePath = filePath;
LoadSector();
}
public ICommand BackCommand { get; }
@@ -81,6 +77,17 @@ public sealed partial class HexViewWindowViewModel : ViewModelBase
public ICommand HelpCommand { get; }
public ICommand ToggleLongCommand { get; }
/// <inheritdoc />
public override void OnNavigatedTo(NavigationContext navigationContext)
{
base.OnNavigatedTo(navigationContext);
_imageFormat = navigationContext.Parameters.GetValue<IMediaImage>("imageFormat");
FilePath = navigationContext.Parameters.GetValue<string>("filePath");
LoadSector();
}
void ToggleLong()
{
_longMode = !_longMode;
@@ -150,7 +157,16 @@ public sealed partial class HexViewWindowViewModel : ViewModelBase
void Back()
{
_view.Close();
IRegion? region = _regionManager.Regions["ContentRegion"];
IRegionNavigationService? navigationService = region.NavigationService;
if(navigationService?.Journal.CanGoBack == true)
navigationService.Journal.GoBack();
else
{
// No history - navigate directly to FileView
_regionManager.RequestNavigate("ContentRegion", nameof(FileView));
}
}
void Exit()
@@ -202,8 +218,6 @@ public sealed partial class HexViewWindowViewModel : ViewModelBase
offset += bytesRead;
}
}
public void LoadComplete() {}
}
public sealed class HexViewLine

View File

@@ -31,8 +31,7 @@ using System.Windows.Input;
using Aaru.CommonTypes;
using Aaru.CommonTypes.Interfaces;
using Aaru.Tui.Models;
using Aaru.Tui.ViewModels.Dialogs;
using Aaru.Tui.Views.Dialogs;
using Aaru.Tui.Views.Windows;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using CommunityToolkit.Mvvm.ComponentModel;
@@ -46,12 +45,13 @@ namespace Aaru.Tui.ViewModels.Windows;
public sealed partial class ImageWindowViewModel : ViewModelBase
{
readonly IMediaImage _imageFormat;
readonly IRegionManager _regionManager;
readonly ManagedWindow _view;
[ObservableProperty]
public string _filePath;
[ObservableProperty]
string _filesystemInformation;
IMediaImage _imageFormat;
[ObservableProperty]
bool _isFilesystemInformationVisible;
[ObservableProperty]
@@ -82,12 +82,9 @@ public sealed partial class ImageWindowViewModel : ViewModelBase
[ObservableProperty]
string? _status;
public ImageWindowViewModel(ManagedWindow view, IMediaImage imageFormat, string filePath)
public ImageWindowViewModel(IRegionManager regionManager)
{
_imageFormat = imageFormat;
FilePath = filePath;
_view = view;
_regionManager = regionManager;
ExitCommand = new RelayCommand(Exit);
BackCommand = new RelayCommand(Back);
HelpCommand = new AsyncRelayCommand(HelpAsync);
@@ -153,7 +150,16 @@ public sealed partial class ImageWindowViewModel : ViewModelBase
void Back()
{
_view.Close();
IRegion? region = _regionManager.Regions["ContentRegion"];
IRegionNavigationService? navigationService = region.NavigationService;
if(navigationService?.Journal.CanGoBack == true)
navigationService.Journal.GoBack();
else
{
// No history - navigate directly to FileView
_regionManager.RequestNavigate("ContentRegion", nameof(FileView));
}
}
void Exit()
@@ -162,13 +168,17 @@ public sealed partial class ImageWindowViewModel : ViewModelBase
lifetime!.Shutdown();
}
public void LoadComplete()
/// <inheritdoc />
public override void OnNavigatedTo(NavigationContext navigationContext)
{
_imageFormat = navigationContext.Parameters.GetValue<IMediaImage>("imageFormat");
FilePath = navigationContext.Parameters.GetValue<string>("filePath");
_ = Task.Run(Worker);
}
Task HelpAsync()
{
Task HelpAsync() =>
/*
var dialog = new ImageHelpDialog
{
DataContext = new ImageHelpDialogViewModel(null!)
@@ -177,8 +187,8 @@ public sealed partial class ImageWindowViewModel : ViewModelBase
// Set the dialog reference after creation
((ImageHelpDialogViewModel)dialog.DataContext!)._dialog = dialog;
return dialog.ShowDialog(_view);
}
return dialog.ShowDialog(_view);*/
Task.CompletedTask;
void Worker()
{

View File

@@ -25,462 +25,6 @@
// Copyright © 2011-2025 Natalia Portillo
// ****************************************************************************/
using System.Collections.ObjectModel;
using System.Reflection;
using System.Text;
using System.Windows.Input;
using Aaru.CommonTypes;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
using Aaru.Core;
using Aaru.Helpers;
using Aaru.Tui.Models;
using Aaru.Tui.ViewModels.Dialogs;
using Aaru.Tui.Views.Dialogs;
using Aaru.Tui.Views.Windows;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Humanizer;
using Humanizer.Bytes;
using Spectre.Console;
using Color = Avalonia.Media.Color;
namespace Aaru.Tui.ViewModels.Windows;
public sealed partial class MainWindowViewModel : ViewModelBase
{
readonly Window _view;
[ObservableProperty]
string _copyright;
[ObservableProperty]
string _currentPath;
[ObservableProperty]
ObservableCollection<FileModel> _files = [];
[ObservableProperty]
string _informationalVersion;
[ObservableProperty]
bool _isStatusVisible;
FileModel? _selectedFile;
[ObservableProperty]
string _status;
public MainWindowViewModel(Window view)
{
_view = view;
ExitCommand = new RelayCommand(Exit);
SectorViewCommand = new RelayCommand(SectorView);
GoToPathCommand = new AsyncRelayCommand(GoToPathAsync);
HelpCommand = new AsyncRelayCommand(HelpAsync);
OpenSelectedFileCommand = new RelayCommand(OpenSelectedFile, CanOpenSelectedFile);
InformationalVersion =
Assembly.GetExecutingAssembly()
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
?.InformationalVersion ??
"?.?.?";
Copyright = Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyCopyrightAttribute>()?.Copyright ?? "";
Status = Localization.Resources.Loading;
}
public FileModel? SelectedFile
{
get => _selectedFile;
set
{
SetProperty(ref _selectedFile, value);
OnPropertyChanged(nameof(IsFileInfoAvailable));
OnPropertyChanged(nameof(SelectedFileIsNotDirectory));
OnPropertyChanged(nameof(SelectedFileLength));
OnPropertyChanged(nameof(SelectedFileCreationTime));
OnPropertyChanged(nameof(SelectedFileLastWriteTime));
OnPropertyChanged(nameof(SelectedFileAttributes));
OnPropertyChanged(nameof(SelectedFileUnixMode));
OnPropertyChanged(nameof(SelectedFileHasInformation));
OnPropertyChanged(nameof(SelectedFileInformation));
}
}
public ICommand OpenSelectedFileCommand { get; }
public ICommand ExitCommand { get; }
public ICommand SectorViewCommand { get; }
public ICommand GoToPathCommand { get; }
public ICommand HelpCommand { get; }
public bool IsFileInfoAvailable => SelectedFile?.FileInfo != null;
public bool SelectedFileIsNotDirectory => SelectedFile?.IsDirectory == false;
public long? SelectedFileLength => SelectedFile?.IsDirectory == false ? SelectedFile?.FileInfo?.Length : 0;
public DateTime? SelectedFileCreationTime => SelectedFile?.FileInfo?.CreationTime;
public DateTime? SelectedFileLastWriteTime => SelectedFile?.FileInfo?.LastWriteTime;
public string? SelectedFileAttributes => SelectedFile?.FileInfo?.Attributes.ToString();
public string? SelectedFileUnixMode => SelectedFile?.FileInfo?.UnixFileMode.ToString();
public bool SelectedFileHasInformation => SelectedFile?.Information != null;
public string? SelectedFileInformation => SelectedFile?.Information;
Task HelpAsync()
{
var dialog = new MainHelpDialog
{
DataContext = new MainHelpDialogViewModel(null!)
};
// Set the dialog reference after creation
((MainHelpDialogViewModel)dialog.DataContext!)._dialog = dialog;
return dialog.ShowDialog(_view);
}
async Task GoToPathAsync()
{
var dialog = new GoToPathDialog
{
DataContext = new GoToPathDialogViewModel(null!)
};
// Set the dialog reference after creation
((GoToPathDialogViewModel)dialog.DataContext!)._dialog = dialog;
bool? result = await dialog.ShowDialog<bool?>(_view);
if(result == true)
{
var viewModel = (GoToPathDialogViewModel)dialog.DataContext;
if(viewModel.Path is not null && Directory.Exists(viewModel.Path))
{
Environment.CurrentDirectory = viewModel.Path;
LoadFiles();
}
}
}
void SectorView()
{
if(SelectedFile?.ImageFormat is null) return;
var view = new HexViewWindow();
var vm = new HexViewWindowViewModel(view, SelectedFile.ImageFormat, SelectedFile.Path);
view.DataContext = vm;
view.Show();
}
void Exit()
{
var lifetime = Application.Current!.ApplicationLifetime as IControlledApplicationLifetime;
lifetime!.Shutdown();
}
public void LoadComplete()
{
LoadFiles();
}
public void LoadFiles()
{
IsStatusVisible = true;
Status = Localization.Resources.Loading;
CurrentPath = Directory.GetCurrentDirectory();
Files.Clear();
var parentDirectory = new FileModel
{
Filename = "..",
Path = Path.GetRelativePath(CurrentPath, ".."),
ForegroundBrush =
new SolidColorBrush(Color.Parse(DirColorsParser.Instance.DirectoryColor ??
DirColorsParser.Instance.NormalColor)),
IsDirectory = true
};
Files.Add(parentDirectory);
foreach(FileModel model in Directory.GetDirectories(CurrentPath, "*", SearchOption.TopDirectoryOnly)
.Select(directory => new FileModel
{
Path = directory,
Filename = Path.GetFileName(directory),
ForegroundBrush =
new SolidColorBrush(Color.Parse(DirColorsParser.Instance
.DirectoryColor ??
DirColorsParser.Instance.NormalColor)),
IsDirectory = true
}))
Files.Add(model);
foreach(string file in Directory.GetFiles(CurrentPath, "*", SearchOption.TopDirectoryOnly))
{
var model = new FileModel
{
Path = file,
Filename = Path.GetFileName(file)
};
string extension = Path.GetExtension(file);
model.ForegroundBrush =
new SolidColorBrush(Color.Parse(DirColorsParser.Instance.ExtensionColors.TryGetValue(extension,
out string? hex)
? hex
: DirColorsParser.Instance.NormalColor));
Files.Add(model);
}
_ = Task.Run(Worker);
}
void Worker()
{
IsStatusVisible = true;
Status = Localization.Resources.Loading_file_information;
foreach(FileModel file in Files)
{
try
{
file.FileInfo = new FileInfo(file.Path);
IFilter inputFilter = PluginRegister.Singleton.GetFilter(file.Path);
if(inputFilter is null) continue;
IBaseImage imageFormat = ImageFormat.Detect(inputFilter);
if(imageFormat is null) continue;
ErrorNumber opened = imageFormat.Open(inputFilter);
if(opened != ErrorNumber.NoError) continue;
StringBuilder sb = new();
if(!string.IsNullOrWhiteSpace(imageFormat.Info.Version))
{
sb.AppendFormat(Aaru.Localization.Core.Format_0_version_1_WithMarkup,
Markup.Escape(imageFormat.Format),
Markup.Escape(imageFormat.Info.Version))
.AppendLine();
}
else
sb.AppendFormat(Aaru.Localization.Core.Format_0_WithMarkup, Markup.Escape(imageFormat.Format))
.AppendLine();
switch(string.IsNullOrWhiteSpace(imageFormat.Info.Application))
{
case false when !string.IsNullOrWhiteSpace(imageFormat.Info.ApplicationVersion):
sb.AppendFormat(Aaru.Localization.Core.Was_created_with_0_version_1_WithMarkup,
Markup.Escape(imageFormat.Info.Application),
Markup.Escape(imageFormat.Info.ApplicationVersion))
.AppendLine();
break;
case false:
sb.AppendFormat(Aaru.Localization.Core.Was_created_with_0_WithMarkup,
Markup.Escape(imageFormat.Info.Application))
.AppendLine();
break;
}
sb.AppendFormat(Aaru.Localization.Core.Image_without_headers_is_0_bytes_long,
imageFormat.Info.ImageSize)
.AppendLine();
sb.AppendFormat(Aaru.Localization.Core.Contains_a_media_of_0_sectors, imageFormat.Info.Sectors)
.AppendLine();
sb.AppendFormat(Aaru.Localization.Core.Maximum_sector_size_of_0_bytes, imageFormat.Info.SectorSize)
.AppendLine();
sb.AppendFormat(Aaru.Localization.Core.Would_be_0_humanized,
ByteSize.FromBytes(imageFormat.Info.Sectors * imageFormat.Info.SectorSize).Humanize())
.AppendLine();
if(!string.IsNullOrWhiteSpace(imageFormat.Info.Creator))
sb.AppendFormat(Aaru.Localization.Core.Created_by_0_WithMarkup,
Markup.Escape(imageFormat.Info.Creator))
.AppendLine();
if(imageFormat.Info.CreationTime != DateTime.MinValue)
sb.AppendFormat(Aaru.Localization.Core.Created_on_0, imageFormat.Info.CreationTime).AppendLine();
if(imageFormat.Info.LastModificationTime != DateTime.MinValue)
sb.AppendFormat(Aaru.Localization.Core.Last_modified_on_0, imageFormat.Info.LastModificationTime)
.AppendLine();
sb.AppendFormat(Aaru.Localization.Core.Contains_a_media_of_type_0,
imageFormat.Info.MediaType.Humanize())
.AppendLine();
sb.AppendFormat(Aaru.Localization.Core.XML_type_0, imageFormat.Info.MetadataMediaType).AppendLine();
sb.AppendLine(imageFormat.Info.HasPartitions
? Aaru.Localization.Core.Has_partitions
: Aaru.Localization.Core.Doesnt_have_partitions);
sb.AppendLine(imageFormat.Info.HasSessions
? Aaru.Localization.Core.Has_sessions
: Aaru.Localization.Core.Doesnt_have_sessions);
if(!string.IsNullOrWhiteSpace(imageFormat.Info.Comments))
sb.AppendFormat(Aaru.Localization.Core.Comments_0_WithMarkup,
Markup.Escape(imageFormat.Info.Comments))
.AppendLine();
if(imageFormat.Info.MediaSequence != 0 && imageFormat.Info.LastMediaSequence != 0)
{
sb.AppendFormat(Aaru.Localization.Core.Media_is_number_0_on_a_set_of_1_medias,
imageFormat.Info.MediaSequence,
imageFormat.Info.LastMediaSequence)
.AppendLine();
}
if(!string.IsNullOrWhiteSpace(imageFormat.Info.MediaTitle))
{
sb.AppendFormat(Aaru.Localization.Core.Media_title_0_WithMarkup,
Markup.Escape(imageFormat.Info.MediaTitle))
.AppendLine();
}
if(!string.IsNullOrWhiteSpace(imageFormat.Info.MediaManufacturer))
{
sb.AppendFormat(Aaru.Localization.Core.Media_manufacturer_0_WithMarkup,
Markup.Escape(imageFormat.Info.MediaManufacturer))
.AppendLine();
}
if(!string.IsNullOrWhiteSpace(imageFormat.Info.MediaModel))
{
sb.AppendFormat(Aaru.Localization.Core.Media_model_0_WithMarkup,
Markup.Escape(imageFormat.Info.MediaModel))
.AppendLine();
}
if(!string.IsNullOrWhiteSpace(imageFormat.Info.MediaSerialNumber))
{
sb.AppendFormat(Aaru.Localization.Core.Media_serial_number_0_WithMarkup,
Markup.Escape(imageFormat.Info.MediaSerialNumber))
.AppendLine();
}
if(!string.IsNullOrWhiteSpace(imageFormat.Info.MediaBarcode))
{
sb.AppendFormat(Aaru.Localization.Core.Media_barcode_0_WithMarkup,
Markup.Escape(imageFormat.Info.MediaBarcode))
.AppendLine();
}
if(!string.IsNullOrWhiteSpace(imageFormat.Info.MediaPartNumber))
{
sb.AppendFormat(Aaru.Localization.Core.Media_part_number_0_WithMarkup,
Markup.Escape(imageFormat.Info.MediaPartNumber))
.AppendLine();
}
if(!string.IsNullOrWhiteSpace(imageFormat.Info.DriveManufacturer))
{
sb.AppendFormat(Aaru.Localization.Core.Drive_manufacturer_0_WithMarkup,
Markup.Escape(imageFormat.Info.DriveManufacturer))
.AppendLine();
}
if(!string.IsNullOrWhiteSpace(imageFormat.Info.DriveModel))
{
sb.AppendFormat(Aaru.Localization.Core.Drive_model_0_WithMarkup,
Markup.Escape(imageFormat.Info.DriveModel))
.AppendLine();
}
if(!string.IsNullOrWhiteSpace(imageFormat.Info.DriveSerialNumber))
{
sb.AppendFormat(Aaru.Localization.Core.Drive_serial_number_0_WithMarkup,
Markup.Escape(imageFormat.Info.DriveSerialNumber))
.AppendLine();
}
if(!string.IsNullOrWhiteSpace(imageFormat.Info.DriveFirmwareRevision))
{
sb.AppendFormat(Aaru.Localization.Core.Drive_firmware_info_0_WithMarkup,
Markup.Escape(imageFormat.Info.DriveFirmwareRevision))
.AppendLine();
}
if(imageFormat.Info.Cylinders > 0 &&
imageFormat.Info is { Heads: > 0, SectorsPerTrack: > 0 } &&
imageFormat.Info.MetadataMediaType != MetadataMediaType.OpticalDisc &&
imageFormat is not ITapeImage { IsTape: true })
{
sb.AppendFormat(Aaru.Localization.Core
.Media_geometry_0_cylinders_1_heads_2_sectors_per_track_WithMarkup,
imageFormat.Info.Cylinders,
imageFormat.Info.Heads,
imageFormat.Info.SectorsPerTrack)
.AppendLine();
}
if(imageFormat.Info.ReadableMediaTags is { Count: > 0 })
{
sb.AppendFormat(Aaru.Localization.Core.Contains_0_readable_media_tags_WithMarkup,
imageFormat.Info.ReadableMediaTags.Count)
.AppendLine();
foreach(MediaTagType tag in imageFormat.Info.ReadableMediaTags.Order())
sb.Append($"[italic][rosybrown]{Markup.Escape(tag.ToString())}[/][/] ");
sb.AppendLine();
}
if(imageFormat.Info.ReadableSectorTags is { Count: > 0 })
{
sb.AppendFormat(Aaru.Localization.Core.Contains_0_readable_sector_tags_WithMarkup,
imageFormat.Info.ReadableSectorTags.Count)
.AppendLine();
foreach(SectorTagType tag in imageFormat.Info.ReadableSectorTags.Order())
sb.Append($"[italic][rosybrown]{Markup.Escape(tag.ToString())}[/][/] ");
sb.AppendLine();
}
file.Information = sb.ToString();
file.ImageFormat = imageFormat as IMediaImage;
}
catch(Exception ex)
{
SentrySdk.CaptureException(ex);
}
}
Status = Localization.Resources.Done;
IsStatusVisible = false;
}
void OpenSelectedFile()
{
if(SelectedFile.IsDirectory)
{
CurrentPath = SelectedFile.Path;
Environment.CurrentDirectory = CurrentPath;
LoadFiles();
return;
}
if(SelectedFile.ImageFormat is null) return;
var imageWindow = new ImageWindow();
var imageViewModel = new ImageWindowViewModel(imageWindow, SelectedFile.ImageFormat, SelectedFile.Path);
imageWindow.DataContext = imageViewModel;
imageWindow.Show();
}
bool CanOpenSelectedFile() => SelectedFile != null;
}
public sealed class MainWindowViewModel {}

View File

@@ -0,0 +1,196 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:console="https://github.com/jinek/consolonia"
xmlns:windows="clr-namespace:Aaru.Tui.ViewModels.Windows"
xmlns:models="clr-namespace:Aaru.Tui.Models"
xmlns:controls="clr-namespace:Aaru.Tui.Controls"
xmlns:localization="clr-namespace:Aaru.Tui.Localization"
xmlns:mvvm="http://prismlibrary.com/"
x:Class="Aaru.Tui.Views.Windows.FileView"
x:DataType="windows:FileViewViewModel"
mvvm:ViewModelLocator.AutoWireViewModel="True">
<Design.DataContext>
<windows:FileViewViewModel />
</Design.DataContext>
<DockPanel>
<Menu DockPanel.Dock="Bottom">
<MenuItem Header="ESC " />
<MenuItem Header="{x:Static localization:Resources.Help_Header}"
Command="{Binding HelpCommand, Mode=OneWay}"
HotKey="F1" />
<MenuItem Header="{x:Static localization:Resources.SectorViewer_Header}"
Command="{Binding SectorViewCommand, Mode=OneWay}"
HotKey="F2" />
<MenuItem Header="F3 " />
<MenuItem Header="{x:Static localization:Resources.GoTo_Header}"
Command="{Binding GoToPathCommand, Mode=OneWay}"
HotKey="F4" />
<MenuItem Header="{x:Static localization:Resources.Exit_Header}"
Command="{Binding ExitCommand, Mode=OneWay}"
HotKey="F10" />
</Menu>
<Border BorderThickness="1">
<Border.BorderBrush>
<console:LineBrush LineStyle="DoubleLine"
Brush="Blue" />
</Border.BorderBrush>
<Grid RowDefinitions="*,Auto">
<Grid ColumnDefinitions="*,*"
Grid.Row="0">
<ListBox Grid.Column="0"
ItemsSource="{Binding Files, Mode=OneWay}"
BorderThickness="1"
Background="Transparent"
SelectedItem="{Binding SelectedFile, Mode=TwoWay}"
KeyDown="ListBox_OnKeyDown">
<ListBox.ItemTemplate>
<DataTemplate DataType="models:FileModel">
<TextBox Text="{Binding Filename, Mode=OneWay}"
Foreground="{Binding ForegroundBrush, Mode=OneWay}"
Background="Transparent" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.BorderBrush>
<console:LineBrush LineStyle="DoubleLine"
Brush="Blue" />
</ListBox.BorderBrush>
</ListBox>
<Border Grid.Column="1"
BorderThickness="1">
<Border.BorderBrush>
<console:LineBrush LineStyle="DoubleLine"
Brush="Blue" />
</Border.BorderBrush>
<Grid RowDefinitions="Auto,Auto,Auto,*"
VerticalAlignment="Top">
<Border Grid.Row="0"
BorderThickness="1">
<Border.BorderBrush>
<console:LineBrush LineStyle="DoubleLine"
Brush="Blue" />
</Border.BorderBrush>
<StackPanel VerticalAlignment="Top"
HorizontalAlignment="Center">
<TextBlock Text="Aaru Data Preservation Suite"
Foreground="Red"
FontWeight="Bold" />
<TextBlock Text="{Binding InformationalVersion, Mode=OneWay}"
Foreground="Green"
FontWeight="Bold" />
<TextBlock Text="{Binding Copyright, Mode=OneWay}"
Foreground="Blue"
FontWeight="Bold" />
</StackPanel>
</Border>
<Border Grid.Row="1"
BorderThickness="1">
<Border.BorderBrush>
<console:LineBrush LineStyle="DoubleLine"
Brush="Blue" />
</Border.BorderBrush>
<Grid ColumnDefinitions="Auto, *">
<TextBlock Grid.Column="0"
Text="Path: "
Foreground="SlateBlue" />
<TextBlock Grid.Column="1"
Text="{Binding CurrentPath, Mode=OneWay}"
Foreground="Green" />
</Grid>
</Border>
<Border Grid.Row="2"
BorderThickness="1"
IsVisible="{Binding IsFileInfoAvailable, Mode=OneWay}">
<Border.BorderBrush>
<console:LineBrush LineStyle="DoubleLine"
Brush="Blue" />
</Border.BorderBrush>
<Grid RowDefinitions="Auto, *, *, *, *">
<TextBlock Text="{x:Static localization:Resources.FileInformation_Text}"
Grid.Row="0"
HorizontalAlignment="Center"
Foreground="SlateBlue" />
<Grid ColumnDefinitions="Auto, *"
Grid.Row="1"
ColumnSpacing="1"
IsVisible="{Binding SelectedFileIsNotDirectory, Mode=OneWay}">
<TextBlock Grid.Column="0"
Text="{x:Static localization:Resources.FileLength_Text}" />
<TextBlock Grid.Column="1"
Text="{Binding SelectedFileLength, Mode=OneWay}"
Foreground="Aqua" />
</Grid>
<Grid ColumnDefinitions="Auto, *"
Grid.Row="2"
ColumnSpacing="1">
<TextBlock Grid.Column="0"
Text="{x:Static localization:Resources.FileCreationTime_Text}" />
<TextBlock Grid.Column="1"
Text="{Binding SelectedFileCreationTime, Mode=OneWay}"
Foreground="Yellow" />
</Grid>
<Grid ColumnDefinitions="Auto, *"
Grid.Row="3"
ColumnSpacing="1">
<TextBlock Grid.Column="0"
Text="{x:Static localization:Resources.FileLastWriteTime_Text}" />
<TextBlock Grid.Column="1"
Text="{Binding SelectedFileLastWriteTime, Mode=OneWay}"
Foreground="Yellow" />
</Grid>
<OnPlatform>
<OnPlatform.Windows>
<Grid ColumnDefinitions="Auto, *"
Grid.Row="4"
ColumnSpacing="1">
<TextBlock Grid.Column="0"
Text="{x:Static localization:Resources.FileAttributes_Text}" />
<TextBlock Grid.Column="1"
Text="{Binding SelectedFileAttributes, Mode=OneWay}"
Foreground="Green" />
</Grid>
</OnPlatform.Windows>
<OnPlatform.Default>
<Grid ColumnDefinitions="Auto, *"
Grid.Row="4"
ColumnSpacing="1">
<TextBlock Grid.Column="0"
Text="{x:Static localization:Resources.FileMode_Text}" />
<TextBlock Grid.Column="1"
Text="{Binding SelectedFileUnixMode, Mode=OneWay}"
Foreground="Green" />
</Grid>
</OnPlatform.Default>
</OnPlatform>
</Grid>
</Border>
<Border Grid.Row="3"
IsVisible="{Binding SelectedFileHasInformation, Mode=OneWay}"
BorderThickness="1">
<Border.BorderBrush>
<console:LineBrush LineStyle="DoubleLine"
Brush="Blue" />
</Border.BorderBrush>
<ScrollViewer>
<StackPanel VerticalAlignment="Top"
HorizontalAlignment="Stretch">
<TextBlock Text="{x:Static localization:Resources.FileInformation_Text}"
Foreground="SlateBlue"
FontWeight="Bold"
HorizontalAlignment="Center" />
<controls:SpectreTextBlock
Text="{Binding SelectedFileInformation, Mode=OneWay}"
HorizontalAlignment="Left"
TextWrapping="Wrap" />
</StackPanel>
</ScrollViewer>
</Border>
</Grid>
</Border>
</Grid>
<TextBlock Grid.Row="1"
Text="{Binding Status, Mode=OneWay}"
IsVisible="{Binding IsStatusVisible, Mode=OneWay}" />
</Grid>
</Border>
</DockPanel>
</UserControl>

View File

@@ -0,0 +1,60 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Text User Interface.
//
// --[ License ] --------------------------------------------------------------
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2025 Natalia Portillo
// ****************************************************************************/
using Aaru.Tui.ViewModels.Windows;
using Avalonia.Controls;
using Avalonia.Input;
namespace Aaru.Tui.Views.Windows;
public partial class FileView : UserControl
{
public FileView()
{
InitializeComponent();
}
private void ListBox_OnKeyDown(object sender, KeyEventArgs e)
{
if(e.Key == Key.Enter)
{
if(DataContext is FileViewViewModel vm && vm.OpenSelectedFileCommand.CanExecute(null))
{
vm.OpenSelectedFileCommand.Execute(null);
e.Handled = true;
}
}
}
/// <inheritdoc />
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
(DataContext as FileViewViewModel)?.LoadComplete();
}
}

View File

@@ -1,12 +1,12 @@
<windowManager:ManagedWindow xmlns="https://github.com/avaloniaui"
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:console="https://github.com/jinek/consolonia"
xmlns:windows="clr-namespace:Aaru.Tui.ViewModels.Windows"
xmlns:windowManager="clr-namespace:Iciclecreek.Avalonia.WindowManager;assembly=Iciclecreek.Avalonia.WindowManager"
xmlns:localization="clr-namespace:Aaru.Tui.Localization"
xmlns:mvvm="http://prismlibrary.com/"
x:Class="Aaru.Tui.Views.Windows.HexViewWindow"
WindowState="FullScreen"
CanResize="False">
x:DataType="windows:HexViewWindowViewModel"
mvvm:ViewModelLocator.AutoWireViewModel="True">
<Design.DataContext>
<windows:HexViewWindowViewModel />
</Design.DataContext>
@@ -101,4 +101,4 @@
</Grid>
</Border>
</DockPanel>
</windowManager:ManagedWindow>
</UserControl>

View File

@@ -25,25 +25,14 @@
// Copyright © 2011-2025 Natalia Portillo
// ****************************************************************************/
using Aaru.Tui.ViewModels.Windows;
using Avalonia.Interactivity;
using Iciclecreek.Avalonia.WindowManager;
using Avalonia.Controls;
namespace Aaru.Tui.Views.Windows;
public partial class HexViewWindow : ManagedWindow
public partial class HexViewWindow : UserControl
{
public HexViewWindow()
{
InitializeComponent();
}
/// <inheritdoc />
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
(DataContext as HexViewWindowViewModel)?.LoadComplete();
}
}

View File

@@ -1,17 +1,17 @@
<windowManager:ManagedWindow xmlns="https://github.com/avaloniaui"
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:windows="clr-namespace:Aaru.Tui.ViewModels.Windows"
xmlns:brushes="https://github.com/jinek/consolonia"
xmlns:windowManager="clr-namespace:Iciclecreek.Avalonia.WindowManager;assembly=Iciclecreek.Avalonia.WindowManager"
xmlns:localization="clr-namespace:Aaru.Tui.Localization"
xmlns:mvvm="http://prismlibrary.com/"
mc:Ignorable="d"
d:DesignWidth="800"
d:DesignHeight="450"
WindowState="FullScreen"
CanResize="False"
x:Class="Aaru.Tui.Views.Windows.ImageWindow">
x:Class="Aaru.Tui.Views.Windows.ImageWindow"
x:DataType="windows:HexViewWindowViewModel"
mvvm:ViewModelLocator.AutoWireViewModel="True">
<Design.DataContext>
<windows:ImageWindowViewModel />
</Design.DataContext>
@@ -174,4 +174,4 @@
</Grid>
</Border>
</DockPanel>
</windowManager:ManagedWindow>
</UserControl>

View File

@@ -25,24 +25,14 @@
// Copyright © 2011-2025 Natalia Portillo
// ****************************************************************************/
using Aaru.Tui.ViewModels.Windows;
using Avalonia.Interactivity;
using Iciclecreek.Avalonia.WindowManager;
using Avalonia.Controls;
namespace Aaru.Tui.Views.Windows;
public partial class ImageWindow : ManagedWindow
public partial class ImageWindow : UserControl
{
public ImageWindow()
{
InitializeComponent();
}
/// <inheritdoc />
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
(DataContext as ImageWindowViewModel)?.LoadComplete();
}
}

View File

@@ -1,195 +1,15 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:console="https://github.com/jinek/consolonia"
xmlns:windows="clr-namespace:Aaru.Tui.ViewModels.Windows"
xmlns:models="clr-namespace:Aaru.Tui.Models"
xmlns:controls="clr-namespace:Aaru.Tui.Controls"
xmlns:localization="clr-namespace:Aaru.Tui.Localization"
xmlns:mvvm="http://prismlibrary.com/"
x:Class="Aaru.Tui.Views.Windows.MainWindow"
RequestedThemeVariant="Dark"
x:DataType="windows:MainWindowViewModel"
mvvm:ViewModelLocator.AutoWireViewModel="True"
Title="Aaru">
<Design.DataContext>
<windows:MainWindowViewModel />
</Design.DataContext>
<DockPanel>
<Menu DockPanel.Dock="Bottom">
<MenuItem Header="ESC " />
<MenuItem Header="{x:Static localization:Resources.Help_Header}"
Command="{Binding HelpCommand, Mode=OneWay}"
HotKey="F1" />
<MenuItem Header="{x:Static localization:Resources.SectorViewer_Header}"
Command="{Binding SectorViewCommand, Mode=OneWay}"
HotKey="F2" />
<MenuItem Header="F3 " />
<MenuItem Header="{x:Static localization:Resources.GoTo_Header}"
Command="{Binding GoToPathCommand, Mode=OneWay}"
HotKey="F4" />
<MenuItem Header="{x:Static localization:Resources.Exit_Header}"
Command="{Binding ExitCommand, Mode=OneWay}"
HotKey="F10" />
</Menu>
<Border BorderThickness="1">
<Border.BorderBrush>
<console:LineBrush LineStyle="DoubleLine"
Brush="Blue" />
</Border.BorderBrush>
<Grid RowDefinitions="*,Auto">
<Grid ColumnDefinitions="*,*"
Grid.Row="0">
<ListBox Grid.Column="0"
ItemsSource="{Binding Files, Mode=OneWay}"
BorderThickness="1"
Background="Transparent"
SelectedItem="{Binding SelectedFile, Mode=TwoWay}"
KeyDown="ListBox_OnKeyDown">
<ListBox.ItemTemplate>
<DataTemplate DataType="models:FileModel">
<TextBox Text="{Binding Filename, Mode=OneWay}"
Foreground="{Binding ForegroundBrush, Mode=OneWay}"
Background="Transparent" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.BorderBrush>
<console:LineBrush LineStyle="DoubleLine"
Brush="Blue" />
</ListBox.BorderBrush>
</ListBox>
<Border Grid.Column="1"
BorderThickness="1">
<Border.BorderBrush>
<console:LineBrush LineStyle="DoubleLine"
Brush="Blue" />
</Border.BorderBrush>
<Grid RowDefinitions="Auto,Auto,Auto,*"
VerticalAlignment="Top">
<Border Grid.Row="0"
BorderThickness="1">
<Border.BorderBrush>
<console:LineBrush LineStyle="DoubleLine"
Brush="Blue" />
</Border.BorderBrush>
<StackPanel VerticalAlignment="Top"
HorizontalAlignment="Center">
<TextBlock Text="Aaru Data Preservation Suite"
Foreground="Red"
FontWeight="Bold" />
<TextBlock Text="{Binding InformationalVersion, Mode=OneWay}"
Foreground="Green"
FontWeight="Bold" />
<TextBlock Text="{Binding Copyright, Mode=OneWay}"
Foreground="Blue"
FontWeight="Bold" />
</StackPanel>
</Border>
<Border Grid.Row="1"
BorderThickness="1">
<Border.BorderBrush>
<console:LineBrush LineStyle="DoubleLine"
Brush="Blue" />
</Border.BorderBrush>
<Grid ColumnDefinitions="Auto, *">
<TextBlock Grid.Column="0"
Text="Path: "
Foreground="SlateBlue" />
<TextBlock Grid.Column="1"
Text="{Binding CurrentPath, Mode=OneWay}"
Foreground="Green" />
</Grid>
</Border>
<Border Grid.Row="2"
BorderThickness="1"
IsVisible="{Binding IsFileInfoAvailable, Mode=OneWay}">
<Border.BorderBrush>
<console:LineBrush LineStyle="DoubleLine"
Brush="Blue" />
</Border.BorderBrush>
<Grid RowDefinitions="Auto, *, *, *, *">
<TextBlock Text="{x:Static localization:Resources.FileInformation_Text}"
Grid.Row="0"
HorizontalAlignment="Center"
Foreground="SlateBlue" />
<Grid ColumnDefinitions="Auto, *"
Grid.Row="1"
ColumnSpacing="1"
IsVisible="{Binding SelectedFileIsNotDirectory, Mode=OneWay}">
<TextBlock Grid.Column="0"
Text="{x:Static localization:Resources.FileLength_Text}" />
<TextBlock Grid.Column="1"
Text="{Binding SelectedFileLength, Mode=OneWay}"
Foreground="Aqua" />
</Grid>
<Grid ColumnDefinitions="Auto, *"
Grid.Row="2"
ColumnSpacing="1">
<TextBlock Grid.Column="0"
Text="{x:Static localization:Resources.FileCreationTime_Text}" />
<TextBlock Grid.Column="1"
Text="{Binding SelectedFileCreationTime, Mode=OneWay}"
Foreground="Yellow" />
</Grid>
<Grid ColumnDefinitions="Auto, *"
Grid.Row="3"
ColumnSpacing="1">
<TextBlock Grid.Column="0"
Text="{x:Static localization:Resources.FileLastWriteTime_Text}" />
<TextBlock Grid.Column="1"
Text="{Binding SelectedFileLastWriteTime, Mode=OneWay}"
Foreground="Yellow" />
</Grid>
<OnPlatform>
<OnPlatform.Windows>
<Grid ColumnDefinitions="Auto, *"
Grid.Row="4"
ColumnSpacing="1">
<TextBlock Grid.Column="0"
Text="{x:Static localization:Resources.FileAttributes_Text}" />
<TextBlock Grid.Column="1"
Text="{Binding SelectedFileAttributes, Mode=OneWay}"
Foreground="Green" />
</Grid>
</OnPlatform.Windows>
<OnPlatform.Default>
<Grid ColumnDefinitions="Auto, *"
Grid.Row="4"
ColumnSpacing="1">
<TextBlock Grid.Column="0"
Text="{x:Static localization:Resources.FileMode_Text}" />
<TextBlock Grid.Column="1"
Text="{Binding SelectedFileUnixMode, Mode=OneWay}"
Foreground="Green" />
</Grid>
</OnPlatform.Default>
</OnPlatform>
</Grid>
</Border>
<Border Grid.Row="3"
IsVisible="{Binding SelectedFileHasInformation, Mode=OneWay}"
BorderThickness="1">
<Border.BorderBrush>
<console:LineBrush LineStyle="DoubleLine"
Brush="Blue" />
</Border.BorderBrush>
<ScrollViewer>
<StackPanel VerticalAlignment="Top"
HorizontalAlignment="Stretch">
<TextBlock Text="{x:Static localization:Resources.FileInformation_Text}"
Foreground="SlateBlue"
FontWeight="Bold"
HorizontalAlignment="Center" />
<controls:SpectreTextBlock
Text="{Binding SelectedFileInformation, Mode=OneWay}"
HorizontalAlignment="Left"
TextWrapping="Wrap" />
</StackPanel>
</ScrollViewer>
</Border>
</Grid>
</Border>
</Grid>
<TextBlock Grid.Row="1"
Text="{Binding Status, Mode=OneWay}"
IsVisible="{Binding IsStatusVisible, Mode=OneWay}" />
</Grid>
</Border>
</DockPanel>
<!-- Prism Region for page navigation and back/forward journaling -->
<ContentControl mvvm:RegionManager.RegionName="ContentRegion" />
</Window>

View File

@@ -25,39 +25,15 @@
// Copyright © 2011-2025 Natalia Portillo
// ****************************************************************************/
using Aaru.Tui.ViewModels.Windows;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
namespace Aaru.Tui.Views.Windows;
/// <summary>Main window view.</summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public MainWindow(object? dataContext) : this() => DataContext = dataContext;
private void ListBox_OnKeyDown(object sender, KeyEventArgs e)
{
if(e.Key == Key.Enter)
{
if(DataContext is MainWindowViewModel vm && vm.OpenSelectedFileCommand.CanExecute(null))
{
vm.OpenSelectedFileCommand.Execute(null);
e.Handled = true;
}
}
}
/// <inheritdoc />
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
(DataContext as MainWindowViewModel)?.LoadComplete();
}
}

View File

@@ -39,6 +39,8 @@
<PackageVersion Include="NUnit3TestAdapter" Version="5.2.0"/>
<PackageVersion Include="Philips.CodeAnalysis.MaintainabilityAnalyzers" Version="1.9.1"/>
<PackageVersion Include="plist-cil" Version="2.3.1"/>
<PackageVersion Include="Prism.Avalonia" Version="9.0.537.11130"/>
<PackageVersion Include="Prism.DryIoc.Avalonia" Version="9.0.537.11130"/>
<PackageVersion Include="Roslynator.Analyzers" Version="4.14.1"/>
<PackageVersion Include="Roslynator.CodeAnalysis.Analyzers" Version="4.14.1"/>
<PackageVersion Include="Roslynator.Formatting.Analyzers" Version="4.14.1"/>