ContextMenu (windows only)
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
using FileTime.Core.Models;
|
||||||
|
|
||||||
|
namespace FileTime.GuiApp.Services;
|
||||||
|
|
||||||
|
public interface IContextMenuProvider
|
||||||
|
{
|
||||||
|
List<object> GetContextMenuForFolder(IContainer container);
|
||||||
|
}
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
<Application
|
<Application
|
||||||
x:Class="FileTime.GuiApp.App.App"
|
x:Class="FileTime.GuiApp.App.App"
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:converters="using:FileTime.GuiApp.Converters"
|
|
||||||
xmlns:local="using:FileTime.GuiApp"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
|
||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
<ResourceDictionary.MergedDictionaries>
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using FileTime.App.Core.Services;
|
using FileTime.App.Core.Services;
|
||||||
using FileTime.App.Core.ViewModels;
|
using FileTime.App.Core.ViewModels;
|
||||||
using FileTime.Core.Interactions;
|
using FileTime.Core.Interactions;
|
||||||
@@ -42,6 +43,15 @@ public static class Startup
|
|||||||
serviceCollection.TryAddSingleton<ISystemClipboardService, SystemClipboardService>();
|
serviceCollection.TryAddSingleton<ISystemClipboardService, SystemClipboardService>();
|
||||||
serviceCollection.TryAddSingleton<IInputInterface>(s => s.GetRequiredService<IDialogService>());
|
serviceCollection.TryAddSingleton<IInputInterface>(s => s.GetRequiredService<IDialogService>());
|
||||||
|
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
serviceCollection.AddSingleton<IContextMenuProvider, WindowsContextMenuProvider>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
serviceCollection.AddSingleton<IContextMenuProvider, LinuxContextMenuProvider>();
|
||||||
|
}
|
||||||
|
|
||||||
return serviceCollection
|
return serviceCollection
|
||||||
.AddSingleton<IStartupHandler, RootDriveInfoService>()
|
.AddSingleton<IStartupHandler, RootDriveInfoService>()
|
||||||
.AddSingleton<IStartupHandler, PlacesService>();
|
.AddSingleton<IStartupHandler, PlacesService>();
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Data.Converters;
|
||||||
|
using FileTime.App.Core.ViewModels;
|
||||||
|
using FileTime.GuiApp.Services;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace FileTime.GuiApp.Converters;
|
||||||
|
|
||||||
|
public class ContextMenuGenerator : IValueConverter
|
||||||
|
{
|
||||||
|
private IContextMenuProvider? _contextMenuProvider;
|
||||||
|
|
||||||
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
_contextMenuProvider ??= DI.ServiceProvider.GetRequiredService<IContextMenuProvider>();
|
||||||
|
|
||||||
|
if (value is IContainerViewModel containerViewModel)
|
||||||
|
{
|
||||||
|
return _contextMenuProvider.GetContextMenuForFolder(containerViewModel.Container);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new object[] { new MenuItem() { Header = "asd" } };
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.14" />
|
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.14" />
|
||||||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.14" />
|
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.14" />
|
||||||
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.13" />
|
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.14" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
||||||
<PackageReference Include="MvvmGen" Version="1.1.5" />
|
<PackageReference Include="MvvmGen" Version="1.1.5" />
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace FileTime.GuiApp.Helper;
|
||||||
|
|
||||||
|
public static class WindowsNativeMethods
|
||||||
|
{
|
||||||
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
|
||||||
|
internal static extern IntPtr LoadLibrary(string lpLibFileName);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
|
||||||
|
internal static extern int LoadString(IntPtr hInstance, uint wID, StringBuilder lpBuffer, int nBufferMax);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
public static extern IntPtr LoadIcon(IntPtr hInstance, IntPtr lpIconName);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
public static extern int FreeLibrary(IntPtr hLibModule);
|
||||||
|
|
||||||
|
[DllImport("shell32.dll")]
|
||||||
|
public static extern IntPtr ExtractAssociatedIcon(IntPtr hInst, StringBuilder lpIconPath, out ushort lpiIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NativeMethodHelpers
|
||||||
|
{
|
||||||
|
public static string GetStringResource(string fileName, uint resourceId)
|
||||||
|
{
|
||||||
|
IntPtr? handle = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
handle = WindowsNativeMethods.LoadLibrary(fileName);
|
||||||
|
StringBuilder buffer = new(8192); //Buffer for output from LoadString()
|
||||||
|
int length = WindowsNativeMethods.LoadString(handle.Value, resourceId, buffer, buffer.Capacity);
|
||||||
|
return buffer.ToString(0, length); //Return the part of the buffer that was used.
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (handle is IntPtr validHandle)
|
||||||
|
{
|
||||||
|
WindowsNativeMethods.FreeLibrary(validHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Icon GetIconResource(string fileName, uint resourceId)
|
||||||
|
{
|
||||||
|
IntPtr? handle = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
handle = WindowsNativeMethods.LoadLibrary(fileName);
|
||||||
|
IntPtr handle2 = WindowsNativeMethods.LoadIcon(handle.Value, new IntPtr(resourceId));
|
||||||
|
return Icon.FromHandle(handle2);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (handle is IntPtr validHandle)
|
||||||
|
{
|
||||||
|
WindowsNativeMethods.FreeLibrary(validHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*public static Icon GetAssociatedIcon()
|
||||||
|
{
|
||||||
|
ushort uicon;
|
||||||
|
StringBuilder strB = new StringBuilder(fileName);
|
||||||
|
IntPtr handle = WindowsNativeMethods.ExtractAssociatedIcon(this.Handle, strB, out uicon);
|
||||||
|
return Icon.FromHandle(handle);
|
||||||
|
}*/
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
using System.Drawing.Imaging;
|
||||||
|
using Avalonia.Media.Imaging;
|
||||||
|
using FileTime.Core.Models;
|
||||||
|
using FileTime.GuiApp.Helper;
|
||||||
|
using FileTime.GuiApp.Models;
|
||||||
|
|
||||||
|
namespace FileTime.GuiApp.IconProviders;
|
||||||
|
|
||||||
|
public static class WindowsSystemIconHelper
|
||||||
|
{
|
||||||
|
public static ImagePath? GetImageByDesktopIni(IContainer folder)
|
||||||
|
{
|
||||||
|
var file = new FileInfo(Path.Combine(folder.NativePath.Path, "desktop.ini"));
|
||||||
|
if (file.Exists)
|
||||||
|
{
|
||||||
|
var lines = File.ReadAllLines(file.FullName);
|
||||||
|
if (Array.Find(lines, l => l.StartsWith("iconresource", StringComparison.OrdinalIgnoreCase)) is string iconLine)
|
||||||
|
{
|
||||||
|
var nameLineValue = string.Join('=', iconLine.Split('=')[1..]);
|
||||||
|
return GetImagePathByIconPath(nameLineValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ImagePath GetImagePathByIconPath(string path)
|
||||||
|
{
|
||||||
|
var environemntVariables = Environment.GetEnvironmentVariables();
|
||||||
|
foreach (var keyo in environemntVariables.Keys)
|
||||||
|
{
|
||||||
|
if (keyo is string key && environemntVariables[key] is string value)
|
||||||
|
{
|
||||||
|
path = path.Replace($"%{key}%", value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var parts = path.Split(',');
|
||||||
|
(var parsedResourceId, var path2) = parts.Length >= 2 && long.TryParse(parts[^1], out var id)
|
||||||
|
? (id, NormalizePath(string.Join(',', parts[..^1])))
|
||||||
|
: (0, NormalizePath(path));
|
||||||
|
|
||||||
|
if (parsedResourceId == 0)
|
||||||
|
{
|
||||||
|
using var extractedIconAsStream = new MemoryStream();
|
||||||
|
using var extractedIcon = System.Drawing.Icon.ExtractAssociatedIcon(path2).ToBitmap();
|
||||||
|
extractedIcon.Save(extractedIconAsStream, ImageFormat.Png);
|
||||||
|
extractedIconAsStream.Position = 0;
|
||||||
|
#pragma warning disable IDISP004 // Don't ignore created IDisposable
|
||||||
|
return new ImagePath(ImagePathType.Raw, new Bitmap(extractedIconAsStream));
|
||||||
|
#pragma warning restore IDISP004 // Don't ignore created IDisposable
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (parsedResourceId < 0) parsedResourceId *= -1;
|
||||||
|
|
||||||
|
using var extractedIcon = NativeMethodHelpers.GetIconResource(path2, (uint)parsedResourceId).ToBitmap();
|
||||||
|
|
||||||
|
using var extractedIconAsStream = new MemoryStream();
|
||||||
|
extractedIcon.Save(extractedIconAsStream, ImageFormat.Png);
|
||||||
|
extractedIconAsStream.Position = 0;
|
||||||
|
|
||||||
|
#pragma warning disable IDISP004 // Don't ignore created IDisposable
|
||||||
|
return new ImagePath(ImagePathType.Raw, new Bitmap(extractedIconAsStream));
|
||||||
|
#pragma warning restore IDISP004 // Don't ignore created IDisposable
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizePath(string path)
|
||||||
|
{
|
||||||
|
if (path.StartsWith('\"') && path.EndsWith('\"'))
|
||||||
|
{
|
||||||
|
return path[1..^1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,13 @@
|
|||||||
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:converters="using:FileTime.GuiApp.Converters">
|
xmlns:converters="using:FileTime.GuiApp.Converters">
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceInclude Source="avares://FileTime.GuiApp/Resources/Brushes.axaml"></ResourceInclude>
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
<converters:ItemViewModeToBrushConverter
|
<converters:ItemViewModeToBrushConverter
|
||||||
AlternativeBrush="{StaticResource AlternativeItemForegroundBrush}"
|
AlternativeBrush="{StaticResource AlternativeItemForegroundBrush}"
|
||||||
DefaultBrush="{StaticResource ForegroundBrush}"
|
DefaultBrush="{StaticResource ForegroundBrush}"
|
||||||
@@ -32,4 +39,5 @@
|
|||||||
<converters:CommandToCommandNameConverter x:Key="CommandToCommandNameConverter" />
|
<converters:CommandToCommandNameConverter x:Key="CommandToCommandNameConverter" />
|
||||||
<converters:ItemToImageConverter x:Key="ItemToImageConverter" />
|
<converters:ItemToImageConverter x:Key="ItemToImageConverter" />
|
||||||
<converters:StringReplaceConverter x:Key="PathPreformatter" OldValue="://" NewValue="/"/>
|
<converters:StringReplaceConverter x:Key="PathPreformatter" OldValue="://" NewValue="/"/>
|
||||||
|
<converters:ContextMenuGenerator x:Key="ContextMenuGenerator"/>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
@@ -1,5 +1,13 @@
|
|||||||
<Styles xmlns="https://github.com/avaloniaui"
|
<Styles xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<Styles.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceInclude Source="avares://FileTime.GuiApp/Resources/Converters.axaml"></ResourceInclude>
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</Styles.Resources>
|
||||||
|
|
||||||
<FluentTheme Mode="Dark" />
|
<FluentTheme Mode="Dark" />
|
||||||
|
|
||||||
<Style Selector="TextBlock">
|
<Style Selector="TextBlock">
|
||||||
@@ -20,15 +28,15 @@
|
|||||||
<Setter Property="Background" Value="Transparent" />
|
<Setter Property="Background" Value="Transparent" />
|
||||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="ListBox.ContentListView > ListBoxItem">
|
<Style Selector="ListBox.ContentListView > ListBoxItem">
|
||||||
<Setter Property="Margin" Value="0" />
|
<Setter Property="Margin" Value="0" />
|
||||||
<Setter Property="Padding" Value="0" />
|
<Setter Property="Padding" Value="0" />
|
||||||
<Setter Property="Background" Value="Transparent" />
|
<Setter Property="Background" Value="Transparent" />
|
||||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||||
<!--Setter Property="ContextMenu">
|
<Setter Property="ContextMenu">
|
||||||
<ContextMenu Items="{Binding Converter={StaticResource ContextMenuGenerator}}"/>
|
<ContextMenu Items="{Binding Converter={StaticResource ContextMenuGenerator}}" />
|
||||||
</Setter-->
|
</Setter>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style Selector="Grid.SidebarContainerPresenter">
|
<Style Selector="Grid.SidebarContainerPresenter">
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using FileTime.Core.Models;
|
||||||
|
|
||||||
|
namespace FileTime.GuiApp.Services;
|
||||||
|
|
||||||
|
public class LinuxContextMenuProvider : IContextMenuProvider
|
||||||
|
{
|
||||||
|
public List<object> GetContextMenuForFolder(IContainer container)
|
||||||
|
{
|
||||||
|
return new List<object>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using FileTime.Core.Models;
|
||||||
|
using FileTime.GuiApp.Helper;
|
||||||
|
using FileTime.GuiApp.IconProviders;
|
||||||
|
using FileTime.Providers.Local;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
|
||||||
|
namespace FileTime.GuiApp.Services;
|
||||||
|
|
||||||
|
public class WindowsContextMenuProvider : IContextMenuProvider
|
||||||
|
{
|
||||||
|
public List<object> GetContextMenuForFolder(IContainer container)
|
||||||
|
{
|
||||||
|
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) throw new NotSupportedException();
|
||||||
|
|
||||||
|
var menuItems = new List<object>();
|
||||||
|
|
||||||
|
if (container.Provider is ILocalContentProvider)
|
||||||
|
{
|
||||||
|
using var directoryKey = Registry.ClassesRoot.OpenSubKey("Directory");
|
||||||
|
ProcessRegistryKey(directoryKey, menuItems, container!.NativePath!.Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return menuItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessRegistryKey(RegistryKey? contextMenuContainer, List<object> menuItems, string folderPath)
|
||||||
|
{
|
||||||
|
using var shell = contextMenuContainer?.OpenSubKey("shell");
|
||||||
|
if (shell == null) return;
|
||||||
|
|
||||||
|
var shellSubKeys = shell.GetSubKeyNames();
|
||||||
|
|
||||||
|
foreach (var shellKey in shellSubKeys.Select(k => shell.OpenSubKey(k)).OfType<RegistryKey>())
|
||||||
|
{
|
||||||
|
var textBase = shellKey.GetValue(null) as string ?? shellKey.GetValue("MUIVerb") as string;
|
||||||
|
|
||||||
|
if (textBase == null) continue;
|
||||||
|
|
||||||
|
string? text = null;
|
||||||
|
if (textBase.StartsWith("@"))
|
||||||
|
{
|
||||||
|
var parts = textBase[1..].Split(',');
|
||||||
|
if (parts.Length == 2 && long.TryParse(parts[1], out var parsedResourceId))
|
||||||
|
{
|
||||||
|
if (parsedResourceId < 0) parsedResourceId *= -1;
|
||||||
|
|
||||||
|
text = NativeMethodHelpers.GetStringResource(string.Join(',', parts[..^1]), (uint) parsedResourceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
text = textBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text != null)
|
||||||
|
{
|
||||||
|
text = text.Replace("&", "");
|
||||||
|
|
||||||
|
object? image = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (shellKey.GetValue("Icon") is string iconPath)
|
||||||
|
{
|
||||||
|
var imagePath = WindowsSystemIconHelper.GetImagePathByIconPath(iconPath);
|
||||||
|
if (imagePath.Type == Models.ImagePathType.Raw)
|
||||||
|
{
|
||||||
|
image = new Image()
|
||||||
|
{
|
||||||
|
Source = (IImage) imagePath.Image!
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
using var commandKey = shellKey.OpenSubKey("command");
|
||||||
|
if (shellKey.GetSubKeyNames().Contains("command") && commandKey?.GetValue(null) is string commandString)
|
||||||
|
{
|
||||||
|
var item = new MenuItem() {Header = text, Icon = image};
|
||||||
|
item.Click += (o, e) => MenuItemClick(folderPath, commandString);
|
||||||
|
menuItems.Add(item);
|
||||||
|
}
|
||||||
|
else if (shellKey.GetValue("ExtendedSubCommandsKey") is string extendedCommands)
|
||||||
|
{
|
||||||
|
var rootMenu = new MenuItem() {Header = text, Icon = image};
|
||||||
|
var rootMenuItems = new List<object>();
|
||||||
|
|
||||||
|
ProcessRegistryKey(Registry.ClassesRoot.OpenSubKey(extendedCommands), rootMenuItems, folderPath);
|
||||||
|
|
||||||
|
rootMenu.Items = rootMenuItems.ToArray();
|
||||||
|
menuItems.Add(rootMenu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void MenuItemClick(string folderPath, string commandString)
|
||||||
|
{
|
||||||
|
var commandPartsWithoutAp = commandString.Split('\"').ToList();
|
||||||
|
var commandParts = new List<List<string>>();
|
||||||
|
|
||||||
|
for (var i = 0; i < commandPartsWithoutAp.Count; i++)
|
||||||
|
{
|
||||||
|
if (i % 2 == 0)
|
||||||
|
{
|
||||||
|
commandParts.Add(commandPartsWithoutAp[i].Split(' ').ToList());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
commandParts.Add(new List<string> {commandPartsWithoutAp[i]});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < commandParts.Count; i++)
|
||||||
|
{
|
||||||
|
for (var i2 = 0; i2 < commandParts[i].Count; i2++)
|
||||||
|
{
|
||||||
|
commandParts[i][i2] = commandParts[i][i2].Replace("%1", folderPath).Replace("%V", folderPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandPartsWithoutEmpty = commandParts.SelectMany(c => c).Where(c => !string.IsNullOrWhiteSpace(c)).ToList();
|
||||||
|
|
||||||
|
if (commandPartsWithoutEmpty.Count == 1)
|
||||||
|
{
|
||||||
|
Process.Start(commandPartsWithoutEmpty[0]);
|
||||||
|
}
|
||||||
|
else if (commandPartsWithoutEmpty.Count > 1)
|
||||||
|
{
|
||||||
|
var paramStartIndex1 = -1;
|
||||||
|
var paramStartIndex2 = -1;
|
||||||
|
var found = false;
|
||||||
|
|
||||||
|
for (var x = 0; x < commandParts.Count && paramStartIndex1 == -1; x++)
|
||||||
|
{
|
||||||
|
for (var y = 0; y < commandParts[x].Count; y++)
|
||||||
|
{
|
||||||
|
if (found)
|
||||||
|
{
|
||||||
|
paramStartIndex1 = x;
|
||||||
|
paramStartIndex2 = y;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (commandParts[x][y] == commandPartsWithoutEmpty[0])
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var arguments = SumList(commandParts, paramStartIndex1, paramStartIndex2);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var process = new Process();
|
||||||
|
process.StartInfo.FileName = commandPartsWithoutEmpty[0];
|
||||||
|
process.StartInfo.Arguments = arguments;
|
||||||
|
process.Start();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
if (commandParts[0].Count > 0)
|
||||||
|
{
|
||||||
|
var executable = "";
|
||||||
|
var lastExecutablePart = 0;
|
||||||
|
for (lastExecutablePart = 0; !File.Exists(executable) && lastExecutablePart < commandParts[0].Count; lastExecutablePart++)
|
||||||
|
{
|
||||||
|
executable += (lastExecutablePart == 0 ? "" : " ") + commandParts[0][lastExecutablePart];
|
||||||
|
}
|
||||||
|
|
||||||
|
lastExecutablePart--;
|
||||||
|
|
||||||
|
if (File.Exists(executable))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var (paramStartX, paramStartY) = GetCoordinatesFrom(commandParts, 1, 0, lastExecutablePart);
|
||||||
|
arguments = SumList(commandParts, paramStartX, paramStartY);
|
||||||
|
|
||||||
|
using var process = new Process();
|
||||||
|
process.StartInfo.FileName = executable;
|
||||||
|
process.StartInfo.Arguments = arguments;
|
||||||
|
process.Start();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
//TODO: error message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//TODO: ELSE error message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string SumList(List<List<string>> data, int paramStartIndex1, int paramStartIndex2)
|
||||||
|
{
|
||||||
|
var result = "";
|
||||||
|
|
||||||
|
for (var x = paramStartIndex1; x < data.Count; x++)
|
||||||
|
{
|
||||||
|
if (x % 2 == 1) result += "\"";
|
||||||
|
|
||||||
|
result += string.Join(
|
||||||
|
' ',
|
||||||
|
x == paramStartIndex1
|
||||||
|
? data[x].Skip(paramStartIndex2)
|
||||||
|
: data[x]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (x % 2 == 1) result += "\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (int, int) GetCoordinatesFrom(List<List<string>> data, int skip, int startX = 0, int startY = 0)
|
||||||
|
{
|
||||||
|
int skipping = 0;
|
||||||
|
var x = startX;
|
||||||
|
var y = startY;
|
||||||
|
|
||||||
|
var resultX = -1;
|
||||||
|
var resultY = -1;
|
||||||
|
for (; x < data.Count && resultX == -1; x++, y = 0)
|
||||||
|
{
|
||||||
|
for (; y < data[x].Count; y++)
|
||||||
|
{
|
||||||
|
if (skipping == skip)
|
||||||
|
{
|
||||||
|
resultX = x;
|
||||||
|
resultY = y;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
skipping++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (resultX, resultY);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,18 @@
|
|||||||
Closed="OnWindowClosed"
|
Closed="OnWindowClosed"
|
||||||
TransparencyLevelHint="Blur"
|
TransparencyLevelHint="Blur"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
<Window.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceInclude Source="avares://FileTime.GuiApp/Resources/SolarizedDarkTheme.axaml"></ResourceInclude>
|
||||||
|
<ResourceInclude Source="avares://FileTime.GuiApp/Resources/Brushes.axaml"></ResourceInclude>
|
||||||
|
<ResourceInclude Source="avares://FileTime.GuiApp/Resources/Converters.axaml"></ResourceInclude>
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</Window.Resources>
|
||||||
|
<Window.Styles>
|
||||||
|
<StyleInclude Source="avares://FileTime.GuiApp/Resources/Styles.axaml" />
|
||||||
|
</Window.Styles>
|
||||||
|
|
||||||
<Grid Background="{DynamicResource AppBackgroundBrush}">
|
<Grid Background="{DynamicResource AppBackgroundBrush}">
|
||||||
<Grid
|
<Grid
|
||||||
@@ -140,7 +152,8 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<Border Grid.Row="1" CornerRadius="10" Background="{DynamicResource ContainerBackgroundBrush}" Padding="0,10" Margin="10">
|
<Border Grid.Row="1" CornerRadius="10" Background="{DynamicResource ContainerBackgroundBrush}"
|
||||||
|
Padding="0,10" Margin="10">
|
||||||
<Grid RowDefinitions="Auto,Auto">
|
<Grid RowDefinitions="Auto,Auto">
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
@@ -152,8 +165,10 @@
|
|||||||
Items="{Binding AppState.Places}">
|
Items="{Binding AppState.Places}">
|
||||||
<ItemsRepeater.ItemTemplate>
|
<ItemsRepeater.ItemTemplate>
|
||||||
<DataTemplate x:DataType="vm:PlaceInfo">
|
<DataTemplate x:DataType="vm:PlaceInfo">
|
||||||
<Grid Classes="SidebarContainerPresenter" PointerPressed="OnHasContainerPointerPressed" Cursor="Hand">
|
<Grid Classes="SidebarContainerPresenter"
|
||||||
<StackPanel Orientation="Horizontal" Margin="10,5" HorizontalAlignment="Stretch">
|
PointerPressed="OnHasContainerPointerPressed" Cursor="Hand">
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="10,5"
|
||||||
|
HorizontalAlignment="Stretch">
|
||||||
<Image
|
<Image
|
||||||
Width="20"
|
Width="20"
|
||||||
Height="20"
|
Height="20"
|
||||||
@@ -265,7 +280,7 @@
|
|||||||
<TextBlock
|
<TextBlock
|
||||||
Margin="5,0,0,0"
|
Margin="5,0,0,0"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="{Binding CurrentLocation^.Name}" />
|
Text="{Binding CurrentLocation^.Name, FallbackValue=Loading...}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<Rectangle
|
<Rectangle
|
||||||
@@ -280,14 +295,7 @@
|
|||||||
<Grid
|
<Grid
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Margin="20,0,0,0">
|
Margin="20,0,0,0">
|
||||||
<Grid>
|
<Grid ColumnDefinitions="15*,10,40*,10,45*">
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="15*" />
|
|
||||||
<ColumnDefinition Width="10" />
|
|
||||||
<ColumnDefinition Width="40*" />
|
|
||||||
<ColumnDefinition Width="10" />
|
|
||||||
<ColumnDefinition Width="45*" />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
|
|
||||||
<ListBox
|
<ListBox
|
||||||
x:CompileBindings="False"
|
x:CompileBindings="False"
|
||||||
@@ -318,7 +326,7 @@
|
|||||||
<Grid
|
<Grid
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
RowDefinitions="Auto,*">
|
RowDefinitions="Auto,*">
|
||||||
<Grid IsVisible="{Binding AppState.SelectedTab^.CurrentLocation^.IsLoading^}">
|
<Grid IsVisible="{Binding AppState.SelectedTab^.CurrentLocation^.IsLoading^, FallbackValue=False}">
|
||||||
<Image
|
<Image
|
||||||
Width="40"
|
Width="40"
|
||||||
Height="40"
|
Height="40"
|
||||||
@@ -422,11 +430,11 @@
|
|||||||
<Grid
|
<Grid
|
||||||
IsVisible="{Binding ItemPreviewService.ItemPreview^,Converter={x:Static ObjectConverters.IsNotNull}}">
|
IsVisible="{Binding ItemPreviewService.ItemPreview^,Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||||
<TextBlock HorizontalAlignment="Center" Text="Don't know how to preview this item."
|
<TextBlock HorizontalAlignment="Center" Text="Don't know how to preview this item."
|
||||||
IsVisible="{Binding ItemPreviewService.ItemPreview^.Mode, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static appCoreModels:ItemPreviewMode.Unknown}}" />
|
IsVisible="{Binding ItemPreviewService.ItemPreview^.Mode, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static appCoreModels:ItemPreviewMode.Unknown},FallbackValue={x:Static appCoreModels:ItemPreviewMode.Unknown}}" />
|
||||||
<TextBlock HorizontalAlignment="Center" Text="Empty"
|
<TextBlock HorizontalAlignment="Center" Text="Empty"
|
||||||
IsVisible="{Binding ItemPreviewService.ItemPreview^.Mode, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static appCoreModels:ItemPreviewMode.Empty}}" />
|
IsVisible="{Binding ItemPreviewService.ItemPreview^.Mode, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static appCoreModels:ItemPreviewMode.Empty},FallbackValue={x:Static appCoreModels:ItemPreviewMode.Unknown}}" />
|
||||||
<ScrollViewer
|
<ScrollViewer
|
||||||
IsVisible="{Binding ItemPreviewService.ItemPreview^.Mode, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static appCoreModels:ItemPreviewMode.Text}}">
|
IsVisible="{Binding ItemPreviewService.ItemPreview^.Mode, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static appCoreModels:ItemPreviewMode.Text},FallbackValue={x:Static appCoreModels:ItemPreviewMode.Unknown}}">
|
||||||
<TextBox
|
<TextBox
|
||||||
IsReadOnly="True"
|
IsReadOnly="True"
|
||||||
x:CompileBindings="False"
|
x:CompileBindings="False"
|
||||||
@@ -439,11 +447,8 @@
|
|||||||
|
|
||||||
<Grid Grid.Row="3">
|
<Grid Grid.Row="3">
|
||||||
<Grid
|
<Grid
|
||||||
IsVisible="{Binding AppState.ViewMode^, Converter={StaticResource EqualityConverter},ConverterParameter=RapidTravel}">
|
IsVisible="{Binding AppState.ViewMode^, Converter={StaticResource EqualityConverter},ConverterParameter=RapidTravel}"
|
||||||
<Grid.RowDefinitions>
|
RowDefinitions="1,Auto">
|
||||||
<RowDefinition Height="1" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<Rectangle
|
<Rectangle
|
||||||
Height="1"
|
Height="1"
|
||||||
|
|||||||
Reference in New Issue
Block a user