mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge branch 'dev' into squirrel_upgrade
This commit is contained in:
commit
9211fe476d
49 changed files with 860 additions and 570 deletions
|
|
@ -75,7 +75,7 @@ namespace Flow.Launcher.Core.ExternalPlugins
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
API.LogInfo(ClassName, $"Fetching from {ManifestFileUrl} was cancelled by caller.");
|
API.LogDebug(ClassName, $"Fetching from {ManifestFileUrl} was cancelled by caller.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
catch (TaskCanceledException)
|
catch (TaskCanceledException)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
@ -14,7 +14,7 @@ using Flow.Launcher.Plugin;
|
||||||
|
|
||||||
namespace Flow.Launcher.Core.Resource
|
namespace Flow.Launcher.Core.Resource
|
||||||
{
|
{
|
||||||
public class Internationalization
|
public class Internationalization : IDisposable
|
||||||
{
|
{
|
||||||
private static readonly string ClassName = nameof(Internationalization);
|
private static readonly string ClassName = nameof(Internationalization);
|
||||||
|
|
||||||
|
|
@ -30,6 +30,7 @@ namespace Flow.Launcher.Core.Resource
|
||||||
private readonly List<string> _languageDirectories = [];
|
private readonly List<string> _languageDirectories = [];
|
||||||
private readonly List<ResourceDictionary> _oldResources = [];
|
private readonly List<ResourceDictionary> _oldResources = [];
|
||||||
private static string SystemLanguageCode;
|
private static string SystemLanguageCode;
|
||||||
|
private readonly SemaphoreSlim _langChangeLock = new(1, 1);
|
||||||
|
|
||||||
public Internationalization(Settings settings)
|
public Internationalization(Settings settings)
|
||||||
{
|
{
|
||||||
|
|
@ -185,20 +186,33 @@ namespace Flow.Launcher.Core.Resource
|
||||||
|
|
||||||
private async Task ChangeLanguageAsync(Language language, bool updateMetadata = true)
|
private async Task ChangeLanguageAsync(Language language, bool updateMetadata = true)
|
||||||
{
|
{
|
||||||
// Remove old language files and load language
|
await _langChangeLock.WaitAsync();
|
||||||
RemoveOldLanguageFiles();
|
|
||||||
if (language != AvailableLanguages.English)
|
try
|
||||||
{
|
{
|
||||||
LoadLanguage(language);
|
// Remove old language files and load language
|
||||||
|
RemoveOldLanguageFiles();
|
||||||
|
if (language != AvailableLanguages.English)
|
||||||
|
{
|
||||||
|
LoadLanguage(language);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change culture info
|
||||||
|
ChangeCultureInfo(language.LanguageCode);
|
||||||
|
|
||||||
|
if (updateMetadata)
|
||||||
|
{
|
||||||
|
// Raise event for plugins after culture is set
|
||||||
|
await Task.Run(UpdatePluginMetadataTranslations);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
// Change culture info
|
|
||||||
ChangeCultureInfo(language.LanguageCode);
|
|
||||||
|
|
||||||
if (updateMetadata)
|
|
||||||
{
|
{
|
||||||
// Raise event for plugins after culture is set
|
API.LogException(ClassName, $"Failed to change language to <{language.LanguageCode}>", e);
|
||||||
await Task.Run(UpdatePluginMetadataTranslations);
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_langChangeLock.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -257,6 +271,7 @@ namespace Flow.Launcher.Core.Resource
|
||||||
{
|
{
|
||||||
dicts.Remove(r);
|
dicts.Remove(r);
|
||||||
}
|
}
|
||||||
|
_oldResources.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadLanguage(Language language)
|
private void LoadLanguage(Language language)
|
||||||
|
|
@ -368,5 +383,15 @@ namespace Flow.Launcher.Core.Resource
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region IDisposable
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
RemoveOldLanguageFiles();
|
||||||
|
_langChangeLock.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using Windows.Win32;
|
|
||||||
|
|
||||||
namespace Flow.Launcher.Infrastructure
|
namespace Flow.Launcher.Infrastructure
|
||||||
{
|
{
|
||||||
|
|
@ -13,9 +9,10 @@ namespace Flow.Launcher.Infrastructure
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string GetActiveExplorerPath()
|
public static string GetActiveExplorerPath()
|
||||||
{
|
{
|
||||||
var explorerWindow = GetActiveExplorer();
|
var explorerPath = DialogJump.DialogJump.GetActiveExplorerPath();
|
||||||
string locationUrl = explorerWindow?.LocationURL;
|
return !string.IsNullOrEmpty(explorerPath) ?
|
||||||
return !string.IsNullOrEmpty(locationUrl) ? GetDirectoryPath(new Uri(locationUrl).LocalPath) : null;
|
GetDirectoryPath(new Uri(explorerPath).LocalPath) :
|
||||||
|
null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -23,74 +20,12 @@ namespace Flow.Launcher.Infrastructure
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static string GetDirectoryPath(string path)
|
private static string GetDirectoryPath(string path)
|
||||||
{
|
{
|
||||||
if (!path.EndsWith("\\"))
|
if (!path.EndsWith('\\'))
|
||||||
{
|
{
|
||||||
return path + "\\";
|
return path + "\\";
|
||||||
}
|
}
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the file explorer that is currently in the foreground
|
|
||||||
/// </summary>
|
|
||||||
private static dynamic GetActiveExplorer()
|
|
||||||
{
|
|
||||||
Type type = Type.GetTypeFromProgID("Shell.Application");
|
|
||||||
if (type == null) return null;
|
|
||||||
dynamic shell = Activator.CreateInstance(type);
|
|
||||||
if (shell == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var explorerWindows = new List<dynamic>();
|
|
||||||
var openWindows = shell.Windows();
|
|
||||||
for (int i = 0; i < openWindows.Count; i++)
|
|
||||||
{
|
|
||||||
var window = openWindows.Item(i);
|
|
||||||
if (window == null) continue;
|
|
||||||
|
|
||||||
// find the desired window and make sure that it is indeed a file explorer
|
|
||||||
// we don't want the Internet Explorer or the classic control panel
|
|
||||||
// ToLower() is needed, because Windows can report the path as "C:\\Windows\\Explorer.EXE"
|
|
||||||
if (Path.GetFileName((string)window.FullName)?.ToLower() == "explorer.exe")
|
|
||||||
{
|
|
||||||
explorerWindows.Add(window);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (explorerWindows.Count == 0) return null;
|
|
||||||
|
|
||||||
var zOrders = GetZOrder(explorerWindows);
|
|
||||||
|
|
||||||
return explorerWindows.Zip(zOrders).MinBy(x => x.Second).First;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the z-order for one or more windows atomically with respect to each other. In Windows, smaller z-order is higher. If the window is not top level, the z order is returned as -1.
|
|
||||||
/// </summary>
|
|
||||||
private static IEnumerable<int> GetZOrder(List<dynamic> hWnds)
|
|
||||||
{
|
|
||||||
var z = new int[hWnds.Count];
|
|
||||||
for (var i = 0; i < hWnds.Count; i++) z[i] = -1;
|
|
||||||
|
|
||||||
var index = 0;
|
|
||||||
var numRemaining = hWnds.Count;
|
|
||||||
PInvoke.EnumWindows((wnd, _) =>
|
|
||||||
{
|
|
||||||
var searchIndex = hWnds.FindIndex(x => new IntPtr(x.HWND) == wnd);
|
|
||||||
if (searchIndex != -1)
|
|
||||||
{
|
|
||||||
z[searchIndex] = index;
|
|
||||||
numRemaining--;
|
|
||||||
if (numRemaining == 0) return false;
|
|
||||||
}
|
|
||||||
index++;
|
|
||||||
return true;
|
|
||||||
}, IntPtr.Zero);
|
|
||||||
|
|
||||||
return z;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ namespace Flow.Launcher.Infrastructure.Image
|
||||||
private static Lock storageLock { get; } = new();
|
private static Lock storageLock { get; } = new();
|
||||||
private static BinaryStorage<List<(string, bool)>> _storage;
|
private static BinaryStorage<List<(string, bool)>> _storage;
|
||||||
private static readonly ConcurrentDictionary<string, string> GuidToKey = new();
|
private static readonly ConcurrentDictionary<string, string> GuidToKey = new();
|
||||||
private static IImageHashGenerator _hashGenerator;
|
private static ImageHashGenerator _hashGenerator;
|
||||||
private static readonly bool EnableImageHash = true;
|
private static readonly bool EnableImageHash = true;
|
||||||
public static ImageSource Image => ImageCache[Constant.ImageIcon, false];
|
public static ImageSource Image => ImageCache[Constant.ImageIcon, false];
|
||||||
public static ImageSource MissingImage => ImageCache[Constant.MissingImgIcon, false];
|
public static ImageSource MissingImage => ImageCache[Constant.MissingImgIcon, false];
|
||||||
|
|
@ -31,7 +31,7 @@ namespace Flow.Launcher.Infrastructure.Image
|
||||||
public const int FullIconSize = 256;
|
public const int FullIconSize = 256;
|
||||||
public const int FullImageSize = 320;
|
public const int FullImageSize = 320;
|
||||||
|
|
||||||
private static readonly string[] ImageExtensions = { ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico" };
|
private static readonly string[] ImageExtensions = [".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico"];
|
||||||
private static readonly string SvgExtension = ".svg";
|
private static readonly string SvgExtension = ".svg";
|
||||||
|
|
||||||
public static async Task InitializeAsync()
|
public static async Task InitializeAsync()
|
||||||
|
|
@ -327,7 +327,7 @@ namespace Flow.Launcher.Infrastructure.Image
|
||||||
return img;
|
return img;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ImageSource LoadFullImage(string path)
|
private static BitmapImage LoadFullImage(string path)
|
||||||
{
|
{
|
||||||
BitmapImage image = new BitmapImage();
|
BitmapImage image = new BitmapImage();
|
||||||
image.BeginInit();
|
image.BeginInit();
|
||||||
|
|
@ -364,7 +364,7 @@ namespace Flow.Launcher.Infrastructure.Image
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ImageSource LoadSvgImage(string path, bool loadFullImage = false)
|
private static RenderTargetBitmap LoadSvgImage(string path, bool loadFullImage = false)
|
||||||
{
|
{
|
||||||
// Set up drawing settings
|
// Set up drawing settings
|
||||||
var desiredHeight = loadFullImage ? FullImageSize : SmallIconSize;
|
var desiredHeight = loadFullImage ? FullImageSize : SmallIconSize;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,18 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
using Flow.Launcher.Plugin;
|
using Flow.Launcher.Plugin;
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace Flow.Launcher.Infrastructure.UserSettings
|
namespace Flow.Launcher.Infrastructure.UserSettings
|
||||||
{
|
{
|
||||||
public class CustomBrowserViewModel : BaseModel
|
public class CustomBrowserViewModel : BaseModel
|
||||||
{
|
{
|
||||||
|
// We should not initialize API in static constructor because it will create another API instance
|
||||||
|
private static IPublicAPI api = null;
|
||||||
|
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
|
||||||
|
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
[JsonIgnore]
|
||||||
|
public string DisplayName => Name == "Default" ? API.GetTranslation("defaultBrowser_default") : Name;
|
||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
public string PrivateArg { get; set; }
|
public string PrivateArg { get; set; }
|
||||||
public bool EnablePrivate { get; set; }
|
public bool EnablePrivate { get; set; }
|
||||||
|
|
@ -26,8 +33,10 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
||||||
Editable = Editable
|
Editable = Editable
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnDisplayNameChanged()
|
||||||
|
{
|
||||||
|
OnPropertyChanged(nameof(DisplayName));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,18 @@
|
||||||
using Flow.Launcher.Plugin;
|
using System.Text.Json.Serialization;
|
||||||
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
|
using Flow.Launcher.Plugin;
|
||||||
|
|
||||||
namespace Flow.Launcher.ViewModel
|
namespace Flow.Launcher.Infrastructure.UserSettings
|
||||||
{
|
{
|
||||||
public class CustomExplorerViewModel : BaseModel
|
public class CustomExplorerViewModel : BaseModel
|
||||||
{
|
{
|
||||||
|
// We should not initialize API in static constructor because it will create another API instance
|
||||||
|
private static IPublicAPI api = null;
|
||||||
|
private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService<IPublicAPI>();
|
||||||
|
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
[JsonIgnore]
|
||||||
|
public string DisplayName => Name == "Explorer" ? API.GetTranslation("fileManagerExplorer") : Name;
|
||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
public string FileArgument { get; set; } = "\"%d\"";
|
public string FileArgument { get; set; } = "\"%d\"";
|
||||||
public string DirectoryArgument { get; set; } = "\"%d\"";
|
public string DirectoryArgument { get; set; } = "\"%d\"";
|
||||||
|
|
@ -21,5 +29,10 @@ namespace Flow.Launcher.ViewModel
|
||||||
Editable = Editable
|
Editable = Editable
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnDisplayNameChanged()
|
||||||
|
{
|
||||||
|
OnPropertyChanged(nameof(DisplayName));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ using Flow.Launcher.Infrastructure.Logger;
|
||||||
using Flow.Launcher.Infrastructure.Storage;
|
using Flow.Launcher.Infrastructure.Storage;
|
||||||
using Flow.Launcher.Plugin;
|
using Flow.Launcher.Plugin;
|
||||||
using Flow.Launcher.Plugin.SharedModels;
|
using Flow.Launcher.Plugin.SharedModels;
|
||||||
using Flow.Launcher.ViewModel;
|
|
||||||
|
|
||||||
namespace Flow.Launcher.Infrastructure.UserSettings
|
namespace Flow.Launcher.Infrastructure.UserSettings
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
@ -904,5 +904,19 @@ namespace Flow.Launcher.Infrastructure
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region File / Folder Dialog
|
||||||
|
|
||||||
|
public static string SelectFile()
|
||||||
|
{
|
||||||
|
var dlg = new OpenFileDialog();
|
||||||
|
var result = dlg.ShowDialog();
|
||||||
|
if (result == true)
|
||||||
|
return dlg.FileName;
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Plugins\Flow.Launcher.Plugin.Calculator\Flow.Launcher.Plugin.Calculator.csproj" />
|
||||||
<ProjectReference Include="..\Plugins\Flow.Launcher.Plugin.Explorer\Flow.Launcher.Plugin.Explorer.csproj" />
|
<ProjectReference Include="..\Plugins\Flow.Launcher.Plugin.Explorer\Flow.Launcher.Plugin.Explorer.csproj" />
|
||||||
<ProjectReference Include="..\Plugins\Flow.Launcher.Plugin.Program\Flow.Launcher.Plugin.Program.csproj" />
|
<ProjectReference Include="..\Plugins\Flow.Launcher.Plugin.Program\Flow.Launcher.Plugin.Program.csproj" />
|
||||||
<ProjectReference Include="..\Plugins\Flow.Launcher.Plugin.Url\Flow.Launcher.Plugin.Url.csproj" />
|
<ProjectReference Include="..\Plugins\Flow.Launcher.Plugin.Url\Flow.Launcher.Plugin.Url.csproj" />
|
||||||
|
|
|
||||||
92
Flow.Launcher.Test/Plugins/CalculatorTest.cs
Normal file
92
Flow.Launcher.Test/Plugins/CalculatorTest.cs
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using Flow.Launcher.Plugin.Calculator;
|
||||||
|
using Mages.Core;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NUnit.Framework.Legacy;
|
||||||
|
|
||||||
|
namespace Flow.Launcher.Test.Plugins
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CalculatorPluginTest
|
||||||
|
{
|
||||||
|
private readonly Main _plugin;
|
||||||
|
private readonly Settings _settings = new()
|
||||||
|
{
|
||||||
|
DecimalSeparator = DecimalSeparator.UseSystemLocale,
|
||||||
|
MaxDecimalPlaces = 10,
|
||||||
|
ShowErrorMessage = false // Make sure we return the empty results when error occurs
|
||||||
|
};
|
||||||
|
private readonly Engine _engine = new(new Configuration
|
||||||
|
{
|
||||||
|
Scope = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "e", Math.E }, // e is not contained in the default mages engine
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
public CalculatorPluginTest()
|
||||||
|
{
|
||||||
|
_plugin = new Main();
|
||||||
|
|
||||||
|
var settingField = typeof(Main).GetField("_settings", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
if (settingField == null)
|
||||||
|
Assert.Fail("Could not find field '_settings' on Flow.Launcher.Plugin.Calculator.Main");
|
||||||
|
settingField.SetValue(_plugin, _settings);
|
||||||
|
|
||||||
|
var engineField = typeof(Main).GetField("MagesEngine", BindingFlags.NonPublic | BindingFlags.Static);
|
||||||
|
if (engineField == null)
|
||||||
|
Assert.Fail("Could not find static field 'MagesEngine' on Flow.Launcher.Plugin.Calculator.Main");
|
||||||
|
engineField.SetValue(null, _engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic operations
|
||||||
|
[TestCase(@"1+1", "2")]
|
||||||
|
[TestCase(@"2-1", "1")]
|
||||||
|
[TestCase(@"2*2", "4")]
|
||||||
|
[TestCase(@"4/2", "2")]
|
||||||
|
[TestCase(@"2^3", "8")]
|
||||||
|
// Decimal places
|
||||||
|
[TestCase(@"10/3", "3.3333333333")]
|
||||||
|
// Parentheses
|
||||||
|
[TestCase(@"(1+2)*3", "9")]
|
||||||
|
[TestCase(@"2^(1+2)", "8")]
|
||||||
|
// Functions
|
||||||
|
[TestCase(@"pow(2,3)", "8")]
|
||||||
|
[TestCase(@"min(1,-1,-2)", "-2")]
|
||||||
|
[TestCase(@"max(1,-1,-2)", "1")]
|
||||||
|
[TestCase(@"sqrt(16)", "4")]
|
||||||
|
[TestCase(@"sin(pi)", "0.0000000000")]
|
||||||
|
[TestCase(@"cos(0)", "1")]
|
||||||
|
[TestCase(@"tan(0)", "0")]
|
||||||
|
[TestCase(@"log10(100)", "2")]
|
||||||
|
[TestCase(@"log(100)", "2")]
|
||||||
|
[TestCase(@"log2(8)", "3")]
|
||||||
|
[TestCase(@"ln(e)", "1")]
|
||||||
|
[TestCase(@"abs(-5)", "5")]
|
||||||
|
// Constants
|
||||||
|
[TestCase(@"pi", "3.1415926536")]
|
||||||
|
// Complex expressions
|
||||||
|
[TestCase(@"(2+3)*sqrt(16)-log(100)/ln(e)", "18")]
|
||||||
|
[TestCase(@"sin(pi/2)+cos(0)+tan(0)", "2")]
|
||||||
|
// Error handling (should return empty result)
|
||||||
|
[TestCase(@"10/0", "")]
|
||||||
|
[TestCase(@"sqrt(-1)", "")]
|
||||||
|
[TestCase(@"log(0)", "")]
|
||||||
|
[TestCase(@"invalid_expression", "")]
|
||||||
|
public void CalculatorTest(string expression, string result)
|
||||||
|
{
|
||||||
|
ClassicAssert.AreEqual(GetCalculationResult(expression), result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetCalculationResult(string expression)
|
||||||
|
{
|
||||||
|
var results = _plugin.Query(new Plugin.Query()
|
||||||
|
{
|
||||||
|
Search = expression
|
||||||
|
});
|
||||||
|
return results.Count > 0 ? results[0].Title : string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -45,6 +45,7 @@ namespace Flow.Launcher
|
||||||
private static Settings _settings;
|
private static Settings _settings;
|
||||||
private static MainWindow _mainWindow;
|
private static MainWindow _mainWindow;
|
||||||
private readonly MainViewModel _mainVM;
|
private readonly MainViewModel _mainVM;
|
||||||
|
private readonly Internationalization _internationalization;
|
||||||
|
|
||||||
// To prevent two disposals running at the same time.
|
// To prevent two disposals running at the same time.
|
||||||
private static readonly object _disposingLock = new();
|
private static readonly object _disposingLock = new();
|
||||||
|
|
@ -107,6 +108,7 @@ namespace Flow.Launcher
|
||||||
API = Ioc.Default.GetRequiredService<IPublicAPI>();
|
API = Ioc.Default.GetRequiredService<IPublicAPI>();
|
||||||
_settings.Initialize();
|
_settings.Initialize();
|
||||||
_mainVM = Ioc.Default.GetRequiredService<MainViewModel>();
|
_mainVM = Ioc.Default.GetRequiredService<MainViewModel>();
|
||||||
|
_internationalization = Ioc.Default.GetRequiredService<Internationalization>();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|
@ -193,7 +195,7 @@ namespace Flow.Launcher
|
||||||
Win32Helper.EnableWin32DarkMode(_settings.ColorScheme);
|
Win32Helper.EnableWin32DarkMode(_settings.ColorScheme);
|
||||||
|
|
||||||
// Initialize language before portable clean up since it needs translations
|
// Initialize language before portable clean up since it needs translations
|
||||||
await Ioc.Default.GetRequiredService<Internationalization>().InitializeLanguageAsync();
|
await _internationalization.InitializeLanguageAsync();
|
||||||
|
|
||||||
Ioc.Default.GetRequiredService<Portable>().PreStartCleanUpAfterPortabilityUpdate();
|
Ioc.Default.GetRequiredService<Portable>().PreStartCleanUpAfterPortabilityUpdate();
|
||||||
|
|
||||||
|
|
@ -421,6 +423,7 @@ namespace Flow.Launcher
|
||||||
_mainWindow?.Dispatcher.Invoke(_mainWindow.Dispose);
|
_mainWindow?.Dispatcher.Invoke(_mainWindow.Dispose);
|
||||||
_mainVM?.Dispose();
|
_mainVM?.Dispose();
|
||||||
DialogJump.Dispose();
|
DialogJump.Dispose();
|
||||||
|
_internationalization.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
API.LogInfo(ClassName, "End Flow Launcher dispose ----------------------------------------------------");
|
API.LogInfo(ClassName, "End Flow Launcher dispose ----------------------------------------------------");
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
|
|
@ -16,7 +17,7 @@ public static class WallpaperPathRetrieval
|
||||||
|
|
||||||
private const int MaxCacheSize = 3;
|
private const int MaxCacheSize = 3;
|
||||||
private static readonly Dictionary<(string, DateTime), ImageBrush> WallpaperCache = new();
|
private static readonly Dictionary<(string, DateTime), ImageBrush> WallpaperCache = new();
|
||||||
private static readonly object CacheLock = new();
|
private static readonly Lock CacheLock = new();
|
||||||
|
|
||||||
public static Brush GetWallpaperBrush()
|
public static Brush GetWallpaperBrush()
|
||||||
{
|
{
|
||||||
|
|
@ -31,7 +32,7 @@ public static class WallpaperPathRetrieval
|
||||||
var wallpaperPath = Win32Helper.GetWallpaperPath();
|
var wallpaperPath = Win32Helper.GetWallpaperPath();
|
||||||
if (string.IsNullOrEmpty(wallpaperPath) || !File.Exists(wallpaperPath))
|
if (string.IsNullOrEmpty(wallpaperPath) || !File.Exists(wallpaperPath))
|
||||||
{
|
{
|
||||||
App.API.LogInfo(ClassName, $"Wallpaper path is invalid: {wallpaperPath}");
|
App.API.LogError(ClassName, $"Wallpaper path is invalid: {wallpaperPath}");
|
||||||
var wallpaperColor = GetWallpaperColor();
|
var wallpaperColor = GetWallpaperColor();
|
||||||
return new SolidColorBrush(wallpaperColor);
|
return new SolidColorBrush(wallpaperColor);
|
||||||
}
|
}
|
||||||
|
|
@ -47,17 +48,22 @@ public static class WallpaperPathRetrieval
|
||||||
return cachedWallpaper;
|
return cachedWallpaper;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using var fileStream = File.OpenRead(wallpaperPath);
|
int originalWidth, originalHeight;
|
||||||
var decoder = BitmapDecoder.Create(fileStream, BitmapCreateOptions.DelayCreation, BitmapCacheOption.None);
|
// Use `using ()` instead of `using var` sentence here to ensure the wallpaper file is not locked
|
||||||
var frame = decoder.Frames[0];
|
using (var fileStream = File.OpenRead(wallpaperPath))
|
||||||
var originalWidth = frame.PixelWidth;
|
{
|
||||||
var originalHeight = frame.PixelHeight;
|
var decoder = BitmapDecoder.Create(fileStream, BitmapCreateOptions.DelayCreation, BitmapCacheOption.None);
|
||||||
|
var frame = decoder.Frames[0];
|
||||||
|
originalWidth = frame.PixelWidth;
|
||||||
|
originalHeight = frame.PixelHeight;
|
||||||
|
}
|
||||||
|
|
||||||
if (originalWidth == 0 || originalHeight == 0)
|
if (originalWidth == 0 || originalHeight == 0)
|
||||||
{
|
{
|
||||||
App.API.LogInfo(ClassName, $"Failed to load bitmap: Width={originalWidth}, Height={originalHeight}");
|
App.API.LogError(ClassName, $"Failed to load bitmap: Width={originalWidth}, Height={originalHeight}");
|
||||||
return new SolidColorBrush(Colors.Transparent);
|
var wallpaperColor = GetWallpaperColor();
|
||||||
|
return new SolidColorBrush(wallpaperColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the scaling factor to fit the image within 800x600 while preserving aspect ratio
|
// Calculate the scaling factor to fit the image within 800x600 while preserving aspect ratio
|
||||||
|
|
@ -70,7 +76,9 @@ public static class WallpaperPathRetrieval
|
||||||
// Set DecodePixelWidth and DecodePixelHeight to resize the image while preserving aspect ratio
|
// Set DecodePixelWidth and DecodePixelHeight to resize the image while preserving aspect ratio
|
||||||
var bitmap = new BitmapImage();
|
var bitmap = new BitmapImage();
|
||||||
bitmap.BeginInit();
|
bitmap.BeginInit();
|
||||||
|
bitmap.CacheOption = BitmapCacheOption.OnLoad; // Use OnLoad to ensure the wallpaper file is not locked
|
||||||
bitmap.UriSource = new Uri(wallpaperPath);
|
bitmap.UriSource = new Uri(wallpaperPath);
|
||||||
|
bitmap.CreateOptions = BitmapCreateOptions.IgnoreColorProfile;
|
||||||
bitmap.DecodePixelWidth = decodedPixelWidth;
|
bitmap.DecodePixelWidth = decodedPixelWidth;
|
||||||
bitmap.DecodePixelHeight = decodedPixelHeight;
|
bitmap.DecodePixelHeight = decodedPixelHeight;
|
||||||
bitmap.EndInit();
|
bitmap.EndInit();
|
||||||
|
|
@ -104,13 +112,13 @@ public static class WallpaperPathRetrieval
|
||||||
|
|
||||||
private static Color GetWallpaperColor()
|
private static Color GetWallpaperColor()
|
||||||
{
|
{
|
||||||
RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Colors", false);
|
using var key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Colors", false);
|
||||||
var result = key?.GetValue("Background", null);
|
var result = key?.GetValue("Background", null);
|
||||||
if (result is string strResult)
|
if (result is string strResult)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var parts = strResult.Trim().Split(new[] { ' ' }, 3).Select(byte.Parse).ToList();
|
var parts = strResult.Trim().Split([' '], 3).Select(byte.Parse).ToList();
|
||||||
return Color.FromRgb(parts[0], parts[1], parts[2]);
|
return Color.FromRgb(parts[0], parts[1], parts[2]);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
|
||||||
|
|
@ -219,6 +219,7 @@
|
||||||
<system:String x:Key="failedToUninstallPluginTitle">Fail to uninstall {0}</system:String>
|
<system:String x:Key="failedToUninstallPluginTitle">Fail to uninstall {0}</system:String>
|
||||||
<system:String x:Key="fileNotFoundMessage">Unable to find plugin.json from the extracted zip file, or this path {0} does not exist</system:String>
|
<system:String x:Key="fileNotFoundMessage">Unable to find plugin.json from the extracted zip file, or this path {0} does not exist</system:String>
|
||||||
<system:String x:Key="pluginExistAlreadyMessage">A plugin with the same ID and version already exists, or the version is greater than this downloaded plugin</system:String>
|
<system:String x:Key="pluginExistAlreadyMessage">A plugin with the same ID and version already exists, or the version is greater than this downloaded plugin</system:String>
|
||||||
|
<system:String x:Key="errorCreatingSettingPanel">Error creating setting panel for plugin {0}:{1}{2}</system:String>
|
||||||
|
|
||||||
<!-- Setting Plugin Store -->
|
<!-- Setting Plugin Store -->
|
||||||
<system:String x:Key="pluginStore">Plugin Store</system:String>
|
<system:String x:Key="pluginStore">Plugin Store</system:String>
|
||||||
|
|
@ -487,6 +488,7 @@
|
||||||
<system:String x:Key="fileManager_file_arg">Arg For File</system:String>
|
<system:String x:Key="fileManager_file_arg">Arg For File</system:String>
|
||||||
<system:String x:Key="fileManagerPathNotFound">The file manager '{0}' could not be located at '{1}'. Would you like to continue?</system:String>
|
<system:String x:Key="fileManagerPathNotFound">The file manager '{0}' could not be located at '{1}'. Would you like to continue?</system:String>
|
||||||
<system:String x:Key="fileManagerPathError">File Manager Path Error</system:String>
|
<system:String x:Key="fileManagerPathError">File Manager Path Error</system:String>
|
||||||
|
<system:String x:Key="fileManagerExplorer">File Explorer</system:String>
|
||||||
|
|
||||||
<!-- DefaultBrowser Setting Dialog -->
|
<!-- DefaultBrowser Setting Dialog -->
|
||||||
<system:String x:Key="defaultBrowserTitle">Default Web Browser</system:String>
|
<system:String x:Key="defaultBrowserTitle">Default Web Browser</system:String>
|
||||||
|
|
@ -497,6 +499,8 @@
|
||||||
<system:String x:Key="defaultBrowser_newWindow">New Window</system:String>
|
<system:String x:Key="defaultBrowser_newWindow">New Window</system:String>
|
||||||
<system:String x:Key="defaultBrowser_newTab">New Tab</system:String>
|
<system:String x:Key="defaultBrowser_newTab">New Tab</system:String>
|
||||||
<system:String x:Key="defaultBrowser_parameter">Private Mode</system:String>
|
<system:String x:Key="defaultBrowser_parameter">Private Mode</system:String>
|
||||||
|
<system:String x:Key="defaultBrowser_default">Default</system:String>
|
||||||
|
<system:String x:Key="defaultBrowser_new_profile">New Profile</system:String>
|
||||||
|
|
||||||
<!-- Priority Setting Dialog -->
|
<!-- Priority Setting Dialog -->
|
||||||
<system:String x:Key="changePriorityWindow">Change Priority</system:String>
|
<system:String x:Key="changePriorityWindow">Change Priority</system:String>
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@
|
||||||
SelectedIndex="{Binding SelectedCustomBrowserIndex}">
|
SelectedIndex="{Binding SelectedCustomBrowserIndex}">
|
||||||
<ComboBox.ItemTemplate>
|
<ComboBox.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<TextBlock Text="{Binding Name}" />
|
<TextBlock Text="{Binding DisplayName}" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ComboBox.ItemTemplate>
|
</ComboBox.ItemTemplate>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
|
using Flow.Launcher.Infrastructure;
|
||||||
using Flow.Launcher.ViewModel;
|
using Flow.Launcher.ViewModel;
|
||||||
|
|
||||||
namespace Flow.Launcher
|
namespace Flow.Launcher
|
||||||
|
|
@ -31,7 +32,7 @@ namespace Flow.Launcher
|
||||||
|
|
||||||
private void btnBrowseFile_Click(object sender, RoutedEventArgs e)
|
private void btnBrowseFile_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var selectedFilePath = _viewModel.SelectFile();
|
var selectedFilePath = Win32Helper.SelectFile();
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(selectedFilePath))
|
if (!string.IsNullOrEmpty(selectedFilePath))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@
|
||||||
SelectedIndex="{Binding SelectedCustomExplorerIndex}">
|
SelectedIndex="{Binding SelectedCustomExplorerIndex}">
|
||||||
<ComboBox.ItemTemplate>
|
<ComboBox.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<TextBlock Text="{Binding Name}" />
|
<TextBlock Text="{Binding DisplayName}" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ComboBox.ItemTemplate>
|
</ComboBox.ItemTemplate>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Navigation;
|
using System.Windows.Navigation;
|
||||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
|
using Flow.Launcher.Infrastructure;
|
||||||
using Flow.Launcher.ViewModel;
|
using Flow.Launcher.ViewModel;
|
||||||
|
|
||||||
namespace Flow.Launcher
|
namespace Flow.Launcher
|
||||||
|
|
@ -32,13 +33,13 @@ namespace Flow.Launcher
|
||||||
|
|
||||||
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
|
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
|
||||||
{
|
{
|
||||||
_viewModel.OpenUrl(e.Uri.AbsoluteUri);
|
App.API.OpenUrl(e.Uri.AbsoluteUri);
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnBrowseFile_Click(object sender, RoutedEventArgs e)
|
private void btnBrowseFile_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var selectedFilePath = _viewModel.SelectFile();
|
var selectedFilePath = Win32Helper.SelectFile();
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(selectedFilePath))
|
if (!string.IsNullOrEmpty(selectedFilePath))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -231,35 +231,41 @@ public partial class SettingsPaneAboutViewModel : BaseModel
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Firstly, delete plugin cache directories
|
// Check if plugin cache directory exists before attempting to delete
|
||||||
pluginCacheDirectory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)
|
// Or it will throw DirectoryNotFoundException in `pluginCacheDirectory.EnumerateDirectories`
|
||||||
.ToList()
|
if (pluginCacheDirectory.Exists)
|
||||||
.ForEach(dir =>
|
{
|
||||||
|
// Firstly, delete plugin cache directories
|
||||||
|
pluginCacheDirectory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)
|
||||||
|
.ToList()
|
||||||
|
.ForEach(dir =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Plugin may create directories in its cache directory
|
||||||
|
dir.Delete(recursive: true);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
App.API.LogException(ClassName, $"Failed to delete cache directory: {dir.Name}", e);
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then, delete plugin directory
|
||||||
|
var dir = pluginCacheDirectory;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try
|
dir.Delete(recursive: false);
|
||||||
{
|
}
|
||||||
// Plugin may create directories in its cache directory
|
catch (Exception e)
|
||||||
dir.Delete(recursive: true);
|
{
|
||||||
}
|
App.API.LogException(ClassName, $"Failed to delete cache directory: {dir.Name}", e);
|
||||||
catch (Exception e)
|
success = false;
|
||||||
{
|
}
|
||||||
App.API.LogException(ClassName, $"Failed to delete cache directory: {dir.Name}", e);
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Then, delete plugin directory
|
|
||||||
var dir = GetPluginCacheDir();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
dir.Delete(recursive: false);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
App.API.LogException(ClassName, $"Failed to delete cache directory: {dir.Name}", e);
|
|
||||||
success = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Raise regardless to cover scenario where size needs to be recalculated if the folder is manually removed on disk.
|
||||||
OnPropertyChanged(nameof(CacheFolderSize));
|
OnPropertyChanged(nameof(CacheFolderSize));
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|
@ -219,6 +219,8 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
|
||||||
DropdownDataGeneric<DialogJumpFileResultBehaviours>.UpdateLabels(DialogJumpFileResultBehaviours);
|
DropdownDataGeneric<DialogJumpFileResultBehaviours>.UpdateLabels(DialogJumpFileResultBehaviours);
|
||||||
// Since we are using Binding instead of DynamicResource, we need to manually trigger the update
|
// Since we are using Binding instead of DynamicResource, we need to manually trigger the update
|
||||||
OnPropertyChanged(nameof(AlwaysPreviewToolTip));
|
OnPropertyChanged(nameof(AlwaysPreviewToolTip));
|
||||||
|
Settings.CustomExplorer.OnDisplayNameChanged();
|
||||||
|
Settings.CustomBrowser.OnDisplayNameChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Language
|
public string Language
|
||||||
|
|
|
||||||
|
|
@ -403,7 +403,7 @@
|
||||||
MaxWidth="250"
|
MaxWidth="250"
|
||||||
Margin="10 0 0 0"
|
Margin="10 0 0 0"
|
||||||
Command="{Binding SelectFileManagerCommand}"
|
Command="{Binding SelectFileManagerCommand}"
|
||||||
Content="{Binding Settings.CustomExplorer.Name}" />
|
Content="{Binding Settings.CustomExplorer.DisplayName}" />
|
||||||
</cc:Card>
|
</cc:Card>
|
||||||
|
|
||||||
<cc:Card
|
<cc:Card
|
||||||
|
|
@ -415,7 +415,7 @@
|
||||||
MaxWidth="250"
|
MaxWidth="250"
|
||||||
Margin="10 0 0 0"
|
Margin="10 0 0 0"
|
||||||
Command="{Binding SelectBrowserCommand}"
|
Command="{Binding SelectBrowserCommand}"
|
||||||
Content="{Binding Settings.CustomBrowser.Name}" />
|
Content="{Binding Settings.CustomBrowser.DisplayName}" />
|
||||||
</cc:Card>
|
</cc:Card>
|
||||||
|
|
||||||
<cc:Card Title="{DynamicResource pythonFilePath}" Margin="0 14 0 0">
|
<cc:Card Title="{DynamicResource pythonFilePath}" Margin="0 14 0 0">
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Threading.Tasks;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
|
|
@ -14,8 +15,13 @@ namespace Flow.Launcher.ViewModel
|
||||||
{
|
{
|
||||||
public partial class PluginViewModel : BaseModel
|
public partial class PluginViewModel : BaseModel
|
||||||
{
|
{
|
||||||
|
private static readonly string ClassName = nameof(PluginViewModel);
|
||||||
|
|
||||||
private static readonly Settings Settings = Ioc.Default.GetRequiredService<Settings>();
|
private static readonly Settings Settings = Ioc.Default.GetRequiredService<Settings>();
|
||||||
|
|
||||||
|
private static readonly Thickness SettingPanelMargin = (Thickness)Application.Current.FindResource("SettingPanelMargin");
|
||||||
|
private static readonly Thickness SettingPanelItemTopBottomMargin = (Thickness)Application.Current.FindResource("SettingPanelItemTopBottomMargin");
|
||||||
|
|
||||||
private readonly PluginPair _pluginPair;
|
private readonly PluginPair _pluginPair;
|
||||||
public PluginPair PluginPair
|
public PluginPair PluginPair
|
||||||
{
|
{
|
||||||
|
|
@ -131,11 +137,30 @@ namespace Flow.Launcher.ViewModel
|
||||||
=> IsExpanded
|
=> IsExpanded
|
||||||
? _settingControl
|
? _settingControl
|
||||||
??= HasSettingControl
|
??= HasSettingControl
|
||||||
? ((ISettingProvider)PluginPair.Plugin).CreateSettingPanel()
|
? TryCreateSettingPanel(PluginPair)
|
||||||
: null
|
: null
|
||||||
: null;
|
: null;
|
||||||
private ImageSource _image = ImageLoader.MissingImage;
|
private ImageSource _image = ImageLoader.MissingImage;
|
||||||
|
|
||||||
|
private static Control TryCreateSettingPanel(PluginPair pair)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// We can safely cast here as we already check this in HasSettingControl
|
||||||
|
return ((ISettingProvider)pair.Plugin).CreateSettingPanel();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// Log exception
|
||||||
|
App.API.LogException(ClassName, $"Failed to create setting panel for {pair.Metadata.Name}", e);
|
||||||
|
|
||||||
|
// Show error message in UI
|
||||||
|
var errorMsg = string.Format(App.API.GetTranslation("errorCreatingSettingPanel"),
|
||||||
|
pair.Metadata.Name, Environment.NewLine, e.Message);
|
||||||
|
return CreateErrorSettingPanel(errorMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Visibility ActionKeywordsVisibility => PluginPair.Metadata.HideActionKeywordPanel ?
|
public Visibility ActionKeywordsVisibility => PluginPair.Metadata.HideActionKeywordPanel ?
|
||||||
Visibility.Collapsed : Visibility.Visible;
|
Visibility.Collapsed : Visibility.Visible;
|
||||||
public string InitializeTime => PluginPair.Metadata.InitTime + "ms";
|
public string InitializeTime => PluginPair.Metadata.InitTime + "ms";
|
||||||
|
|
@ -186,5 +211,28 @@ namespace Flow.Launcher.ViewModel
|
||||||
var changeKeywordsWindow = new ActionKeywords(this);
|
var changeKeywordsWindow = new ActionKeywords(this);
|
||||||
changeKeywordsWindow.ShowDialog();
|
changeKeywordsWindow.ShowDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static UserControl CreateErrorSettingPanel(string text)
|
||||||
|
{
|
||||||
|
var grid = new Grid()
|
||||||
|
{
|
||||||
|
Margin = SettingPanelMargin
|
||||||
|
};
|
||||||
|
var textBox = new TextBox
|
||||||
|
{
|
||||||
|
Text = text,
|
||||||
|
IsReadOnly = true,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||||
|
VerticalAlignment = VerticalAlignment.Top,
|
||||||
|
TextWrapping = TextWrapping.Wrap,
|
||||||
|
Margin = SettingPanelItemTopBottomMargin
|
||||||
|
};
|
||||||
|
textBox.SetResourceReference(TextBox.ForegroundProperty, "Color04B");
|
||||||
|
grid.Children.Add(textBox);
|
||||||
|
return new UserControl
|
||||||
|
{
|
||||||
|
Content = grid
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,13 @@ public partial class SelectBrowserViewModel : BaseModel
|
||||||
get => selectedCustomBrowserIndex;
|
get => selectedCustomBrowserIndex;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
selectedCustomBrowserIndex = value;
|
// When one custom browser is selected and removed, the index will become -1, so we need to ignore this change
|
||||||
OnPropertyChanged(nameof(CustomBrowser));
|
if (value < 0) return;
|
||||||
|
if (selectedCustomBrowserIndex != value)
|
||||||
|
{
|
||||||
|
selectedCustomBrowserIndex = value;
|
||||||
|
OnPropertyChanged(nameof(CustomBrowser));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,22 +45,12 @@ public partial class SelectBrowserViewModel : BaseModel
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal string SelectFile()
|
|
||||||
{
|
|
||||||
var dlg = new Microsoft.Win32.OpenFileDialog();
|
|
||||||
var result = dlg.ShowDialog();
|
|
||||||
if (result == true)
|
|
||||||
return dlg.FileName;
|
|
||||||
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void Add()
|
private void Add()
|
||||||
{
|
{
|
||||||
CustomBrowsers.Add(new()
|
CustomBrowsers.Add(new()
|
||||||
{
|
{
|
||||||
Name = "New Profile"
|
Name = App.API.GetTranslation("defaultBrowser_new_profile")
|
||||||
});
|
});
|
||||||
SelectedCustomBrowserIndex = CustomBrowsers.Count - 1;
|
SelectedCustomBrowserIndex = CustomBrowsers.Count - 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ public partial class SelectFileManagerViewModel : BaseModel
|
||||||
get => selectedCustomExplorerIndex;
|
get => selectedCustomExplorerIndex;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
|
// When one custom file manager is selected and removed, the index will become -1, so we need to ignore this change
|
||||||
|
if (value < 0) return;
|
||||||
if (selectedCustomExplorerIndex != value)
|
if (selectedCustomExplorerIndex != value)
|
||||||
{
|
{
|
||||||
selectedCustomExplorerIndex = value;
|
selectedCustomExplorerIndex = value;
|
||||||
|
|
@ -98,27 +100,12 @@ public partial class SelectFileManagerViewModel : BaseModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void OpenUrl(string absoluteUri)
|
|
||||||
{
|
|
||||||
App.API.OpenUrl(absoluteUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal string SelectFile()
|
|
||||||
{
|
|
||||||
var dlg = new Microsoft.Win32.OpenFileDialog();
|
|
||||||
var result = dlg.ShowDialog();
|
|
||||||
if (result == true)
|
|
||||||
return dlg.FileName;
|
|
||||||
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void Add()
|
private void Add()
|
||||||
{
|
{
|
||||||
CustomExplorers.Add(new()
|
CustomExplorers.Add(new()
|
||||||
{
|
{
|
||||||
Name = "New Profile"
|
Name = App.API.GetTranslation("defaultBrowser_new_profile")
|
||||||
});
|
});
|
||||||
SelectedCustomExplorerIndex = CustomExplorers.Count - 1;
|
SelectedCustomExplorerIndex = CustomExplorers.Count - 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||||
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.5" />
|
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.6" />
|
||||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.9" />
|
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.9" />
|
||||||
<PackageReference Include="Svg.Skia" Version="3.0.6" />
|
<PackageReference Include="Svg.Skia" Version="3.0.6" />
|
||||||
<PackageReference Include="SkiaSharp" Version="3.119.0" />
|
<PackageReference Include="SkiaSharp" Version="3.119.0" />
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.5" />
|
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.6" />
|
||||||
<PackageReference Include="Mages" Version="3.0.0" />
|
<PackageReference Include="Mages" Version="3.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
xmlns:system="clr-namespace:System;assembly=mscorlib">
|
xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||||
|
|
||||||
<system:String x:Key="flowlauncher_plugin_calculator_plugin_name">Calculator</system:String>
|
<system:String x:Key="flowlauncher_plugin_calculator_plugin_name">Calculator</system:String>
|
||||||
<system:String x:Key="flowlauncher_plugin_calculator_plugin_description">Perform mathematical calculations (including hexadecimal values). Use ',' or '.' as thousand separator or decimal place.</system:String>
|
<system:String x:Key="flowlauncher_plugin_calculator_plugin_description">Perform mathematical calculations, including hex values and advanced functions such as 'min(1,2,3)', 'sqrt(123)' and 'cos(123)'.</system:String>
|
||||||
<system:String x:Key="flowlauncher_plugin_calculator_not_a_number">Not a number (NaN)</system:String>
|
<system:String x:Key="flowlauncher_plugin_calculator_not_a_number">Not a number (NaN)</system:String>
|
||||||
<system:String x:Key="flowlauncher_plugin_calculator_expression_not_complete">Expression wrong or incomplete (Did you forget some parentheses?)</system:String>
|
<system:String x:Key="flowlauncher_plugin_calculator_expression_not_complete">Expression wrong or incomplete (Did you forget some parentheses?)</system:String>
|
||||||
<system:String x:Key="flowlauncher_plugin_calculator_copy_number_to_clipboard">Copy this number to the clipboard</system:String>
|
<system:String x:Key="flowlauncher_plugin_calculator_copy_number_to_clipboard">Copy this number to the clipboard</system:String>
|
||||||
|
|
@ -15,4 +15,5 @@
|
||||||
<system:String x:Key="flowlauncher_plugin_calculator_decimal_separator_dot">Dot (.)</system:String>
|
<system:String x:Key="flowlauncher_plugin_calculator_decimal_separator_dot">Dot (.)</system:String>
|
||||||
<system:String x:Key="flowlauncher_plugin_calculator_max_decimal_places">Max. decimal places</system:String>
|
<system:String x:Key="flowlauncher_plugin_calculator_max_decimal_places">Max. decimal places</system:String>
|
||||||
<system:String x:Key="flowlauncher_plugin_calculator_failed_to_copy">Copy failed, please try later</system:String>
|
<system:String x:Key="flowlauncher_plugin_calculator_failed_to_copy">Copy failed, please try later</system:String>
|
||||||
|
<system:String x:Key="flowlauncher_plugin_calculator_show_error_message">Show error message when calculation fails</system:String>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|
@ -13,30 +13,24 @@ namespace Flow.Launcher.Plugin.Calculator
|
||||||
{
|
{
|
||||||
public class Main : IPlugin, IPluginI18n, ISettingProvider
|
public class Main : IPlugin, IPluginI18n, ISettingProvider
|
||||||
{
|
{
|
||||||
private static readonly Regex RegValidExpressChar = MainRegexHelper.GetRegValidExpressChar();
|
|
||||||
private static readonly Regex RegBrackets = MainRegexHelper.GetRegBrackets();
|
|
||||||
private static readonly Regex ThousandGroupRegex = MainRegexHelper.GetThousandGroupRegex();
|
private static readonly Regex ThousandGroupRegex = MainRegexHelper.GetThousandGroupRegex();
|
||||||
private static readonly Regex NumberRegex = MainRegexHelper.GetNumberRegex();
|
private static readonly Regex NumberRegex = MainRegexHelper.GetNumberRegex();
|
||||||
|
private static readonly Regex PowRegex = MainRegexHelper.GetPowRegex();
|
||||||
|
private static readonly Regex LogRegex = MainRegexHelper.GetLogRegex();
|
||||||
|
private static readonly Regex LnRegex = MainRegexHelper.GetLnRegex();
|
||||||
|
private static readonly Regex FunctionRegex = MainRegexHelper.GetFunctionRegex();
|
||||||
|
|
||||||
private static Engine MagesEngine;
|
private static Engine MagesEngine;
|
||||||
private const string Comma = ",";
|
private const string Comma = ",";
|
||||||
private const string Dot = ".";
|
private const string Dot = ".";
|
||||||
|
private const string IcoPath = "Images/calculator.png";
|
||||||
|
private static readonly List<Result> EmptyResults = [];
|
||||||
|
|
||||||
internal static PluginInitContext Context { get; set; } = null!;
|
internal static PluginInitContext Context { get; set; } = null!;
|
||||||
|
|
||||||
private Settings _settings;
|
private Settings _settings;
|
||||||
private SettingsViewModel _viewModel;
|
private SettingsViewModel _viewModel;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Holds the formatting information for a single query.
|
|
||||||
/// This is used to ensure thread safety by keeping query state local.
|
|
||||||
/// </summary>
|
|
||||||
private class ParsingContext
|
|
||||||
{
|
|
||||||
public string InputDecimalSeparator { get; set; }
|
|
||||||
public bool InputUsesGroupSeparators { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Init(PluginInitContext context)
|
public void Init(PluginInitContext context)
|
||||||
{
|
{
|
||||||
Context = context;
|
Context = context;
|
||||||
|
|
@ -54,38 +48,98 @@ namespace Flow.Launcher.Plugin.Calculator
|
||||||
|
|
||||||
public List<Result> Query(Query query)
|
public List<Result> Query(Query query)
|
||||||
{
|
{
|
||||||
if (!CanCalculate(query))
|
if (string.IsNullOrWhiteSpace(query.Search))
|
||||||
{
|
{
|
||||||
return new List<Result>();
|
return EmptyResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
var context = new ParsingContext();
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var expression = NumberRegex.Replace(query.Search, m => NormalizeNumber(m.Value, context));
|
var search = query.Search;
|
||||||
|
bool isFunctionPresent = FunctionRegex.IsMatch(search);
|
||||||
|
|
||||||
|
// Mages is case sensitive, so we need to convert all function names to lower case.
|
||||||
|
search = FunctionRegex.Replace(search, m => m.Value.ToLowerInvariant());
|
||||||
|
|
||||||
|
var decimalSep = GetDecimalSeparator();
|
||||||
|
var groupSep = GetGroupSeparator(decimalSep);
|
||||||
|
var expression = NumberRegex.Replace(search, m => NormalizeNumber(m.Value, isFunctionPresent, decimalSep, groupSep));
|
||||||
|
|
||||||
|
// WORKAROUND START: The 'pow' function in Mages v3.0.0 is broken.
|
||||||
|
// https://github.com/FlorianRappl/Mages/issues/132
|
||||||
|
// We bypass it by rewriting any pow(x,y) expression to the equivalent (x^y) expression
|
||||||
|
// before the engine sees it. This loop handles nested calls.
|
||||||
|
{
|
||||||
|
string previous;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
previous = expression;
|
||||||
|
expression = PowRegex.Replace(previous, PowMatchEvaluator);
|
||||||
|
} while (previous != expression);
|
||||||
|
}
|
||||||
|
// WORKAROUND END
|
||||||
|
|
||||||
|
// WORKAROUND START: The 'log' & 'ln' function in Mages v3.0.0 are broken.
|
||||||
|
// https://github.com/FlorianRappl/Mages/issues/137
|
||||||
|
// We bypass it by rewriting any log & ln expression to the equivalent (log10 & log) expression
|
||||||
|
// before the engine sees it. This loop handles nested calls.
|
||||||
|
{
|
||||||
|
string previous;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
previous = expression;
|
||||||
|
expression = LogRegex.Replace(previous, LogMatchEvaluator);
|
||||||
|
} while (previous != expression);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
string previous;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
previous = expression;
|
||||||
|
expression = LnRegex.Replace(previous, LnMatchEvaluator);
|
||||||
|
} while (previous != expression);
|
||||||
|
}
|
||||||
|
// WORKAROUND END
|
||||||
|
|
||||||
var result = MagesEngine.Interpret(expression);
|
var result = MagesEngine.Interpret(expression);
|
||||||
|
|
||||||
if (result?.ToString() == "NaN")
|
if (result == null || string.IsNullOrEmpty(result.ToString()))
|
||||||
|
{
|
||||||
|
if (!_settings.ShowErrorMessage) return EmptyResults;
|
||||||
|
return
|
||||||
|
[
|
||||||
|
new Result
|
||||||
|
{
|
||||||
|
Title = Localize.flowlauncher_plugin_calculator_expression_not_complete(),
|
||||||
|
IcoPath = IcoPath
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.ToString() == "NaN")
|
||||||
|
{
|
||||||
result = Localize.flowlauncher_plugin_calculator_not_a_number();
|
result = Localize.flowlauncher_plugin_calculator_not_a_number();
|
||||||
|
}
|
||||||
|
|
||||||
if (result is Function)
|
if (result is Function)
|
||||||
|
{
|
||||||
result = Localize.flowlauncher_plugin_calculator_expression_not_complete();
|
result = Localize.flowlauncher_plugin_calculator_expression_not_complete();
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(result?.ToString()))
|
if (!string.IsNullOrEmpty(result.ToString()))
|
||||||
{
|
{
|
||||||
decimal roundedResult = Math.Round(Convert.ToDecimal(result), _settings.MaxDecimalPlaces, MidpointRounding.AwayFromZero);
|
decimal roundedResult = Math.Round(Convert.ToDecimal(result), _settings.MaxDecimalPlaces, MidpointRounding.AwayFromZero);
|
||||||
string newResult = FormatResult(roundedResult, context);
|
string newResult = FormatResult(roundedResult);
|
||||||
|
|
||||||
return new List<Result>
|
return
|
||||||
{
|
[
|
||||||
new Result
|
new Result
|
||||||
{
|
{
|
||||||
Title = newResult,
|
Title = newResult,
|
||||||
IcoPath = "Images/calculator.png",
|
IcoPath = IcoPath,
|
||||||
Score = 300,
|
Score = 300,
|
||||||
SubTitle = Localize.flowlauncher_plugin_calculator_copy_number_to_clipboard(),
|
// Check context nullability for unit testing
|
||||||
|
SubTitle = Context == null ? string.Empty : Localize.flowlauncher_plugin_calculator_copy_number_to_clipboard(),
|
||||||
CopyText = newResult,
|
CopyText = newResult,
|
||||||
Action = c =>
|
Action = c =>
|
||||||
{
|
{
|
||||||
|
|
@ -101,118 +155,206 @@ namespace Flow.Launcher.Plugin.Calculator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
// ignored
|
// Mages engine can throw various exceptions, for simplicity we catch them all and show a generic message.
|
||||||
|
if (!_settings.ShowErrorMessage) return EmptyResults;
|
||||||
|
return
|
||||||
|
[
|
||||||
|
new Result
|
||||||
|
{
|
||||||
|
Title = Localize.flowlauncher_plugin_calculator_expression_not_complete(),
|
||||||
|
IcoPath = IcoPath
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return new List<Result>();
|
return EmptyResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private static string PowMatchEvaluator(Match m)
|
||||||
/// Parses a string representation of a number, detecting its format. It uses structural analysis
|
|
||||||
/// and falls back to system culture for truly ambiguous cases (e.g., "1,234").
|
|
||||||
/// It populates the provided ParsingContext with the detected format for later use.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A normalized number string with '.' as the decimal separator for the Mages engine.</returns>
|
|
||||||
private string NormalizeNumber(string numberStr, ParsingContext context)
|
|
||||||
{
|
{
|
||||||
var systemGroupSep = CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator;
|
// m.Groups[1].Value will be `(...)` with parens
|
||||||
int dotCount = numberStr.Count(f => f == '.');
|
var contentWithParen = m.Groups[1].Value;
|
||||||
int commaCount = numberStr.Count(f => f == ',');
|
// remove outer parens. `(min(2,3), 4)` becomes `min(2,3), 4`
|
||||||
|
var argsContent = contentWithParen[1..^1];
|
||||||
|
|
||||||
// Case 1: Unambiguous mixed separators (e.g., "1.234,56")
|
var bracketCount = 0;
|
||||||
if (dotCount > 0 && commaCount > 0)
|
var splitIndex = -1;
|
||||||
|
|
||||||
|
// Find the top-level comma that separates the two arguments of pow.
|
||||||
|
for (var i = 0; i < argsContent.Length; i++)
|
||||||
{
|
{
|
||||||
context.InputUsesGroupSeparators = true;
|
switch (argsContent[i])
|
||||||
if (numberStr.LastIndexOf('.') > numberStr.LastIndexOf(','))
|
|
||||||
{
|
{
|
||||||
context.InputDecimalSeparator = Dot;
|
case '(':
|
||||||
return numberStr.Replace(Comma, string.Empty);
|
case '[':
|
||||||
|
bracketCount++;
|
||||||
|
break;
|
||||||
|
case ')':
|
||||||
|
case ']':
|
||||||
|
bracketCount--;
|
||||||
|
break;
|
||||||
|
case ',' when bracketCount == 0:
|
||||||
|
splitIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (splitIndex != -1)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (splitIndex == -1)
|
||||||
|
{
|
||||||
|
// This indicates malformed arguments for pow, e.g., pow(5) or pow().
|
||||||
|
// Return original string to let Mages handle the error.
|
||||||
|
return m.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
var arg1 = argsContent[..splitIndex].Trim();
|
||||||
|
var arg2 = argsContent[(splitIndex + 1)..].Trim();
|
||||||
|
|
||||||
|
// Check for empty arguments which can happen with stray commas, e.g., pow(,5)
|
||||||
|
if (string.IsNullOrEmpty(arg1) || string.IsNullOrEmpty(arg2))
|
||||||
|
{
|
||||||
|
return m.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"({arg1}^{arg2})";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string LogMatchEvaluator(Match m)
|
||||||
|
{
|
||||||
|
// m.Groups[1].Value will be `(...)` with parens
|
||||||
|
var contentWithParen = m.Groups[1].Value;
|
||||||
|
var argsContent = contentWithParen[1..^1];
|
||||||
|
|
||||||
|
// log is unary — if malformed, return original to let Mages handle it
|
||||||
|
var arg = argsContent.Trim();
|
||||||
|
if (string.IsNullOrEmpty(arg)) return m.Value;
|
||||||
|
|
||||||
|
// log(x) -> log10(x) (natural log)
|
||||||
|
return $"(log10({arg}))";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string LnMatchEvaluator(Match m)
|
||||||
|
{
|
||||||
|
// m.Groups[1].Value will be `(...)` with parens
|
||||||
|
var contentWithParen = m.Groups[1].Value;
|
||||||
|
var argsContent = contentWithParen[1..^1];
|
||||||
|
|
||||||
|
// ln is unary — if malformed, return original to let Mages handle it
|
||||||
|
var arg = argsContent.Trim();
|
||||||
|
if (string.IsNullOrEmpty(arg)) return m.Value;
|
||||||
|
|
||||||
|
// ln(x) -> log(x) (natural log)
|
||||||
|
return $"(log({arg}))";
|
||||||
|
}
|
||||||
|
private static string NormalizeNumber(string numberStr, bool isFunctionPresent, string decimalSep, string groupSep)
|
||||||
|
{
|
||||||
|
if (isFunctionPresent)
|
||||||
|
{
|
||||||
|
// STRICT MODE: When functions are present, ',' is ALWAYS an argument separator.
|
||||||
|
if (numberStr.Contains(','))
|
||||||
|
{
|
||||||
|
return numberStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
string processedStr = numberStr;
|
||||||
|
|
||||||
|
// Handle group separator, with special care for ambiguous dot.
|
||||||
|
if (!string.IsNullOrEmpty(groupSep))
|
||||||
|
{
|
||||||
|
if (groupSep == ".")
|
||||||
|
{
|
||||||
|
var parts = processedStr.Split('.');
|
||||||
|
if (parts.Length > 1)
|
||||||
|
{
|
||||||
|
var culture = CultureInfo.CurrentCulture;
|
||||||
|
if (IsValidGrouping(parts, culture.NumberFormat.NumberGroupSizes))
|
||||||
|
{
|
||||||
|
processedStr = processedStr.Replace(groupSep, "");
|
||||||
|
}
|
||||||
|
// If not grouped, it's likely a decimal number, so we don't strip dots.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
processedStr = processedStr.Replace(groupSep, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle decimal separator.
|
||||||
|
if (decimalSep != ".")
|
||||||
|
{
|
||||||
|
processedStr = processedStr.Replace(decimalSep, ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
return processedStr;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// LENIENT MODE: No functions are present, so we can be flexible.
|
||||||
|
string processedStr = numberStr;
|
||||||
|
if (!string.IsNullOrEmpty(groupSep))
|
||||||
|
{
|
||||||
|
processedStr = processedStr.Replace(groupSep, "");
|
||||||
|
}
|
||||||
|
if (decimalSep != ".")
|
||||||
|
{
|
||||||
|
processedStr = processedStr.Replace(decimalSep, ".");
|
||||||
|
}
|
||||||
|
return processedStr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsValidGrouping(string[] parts, int[] groupSizes)
|
||||||
|
{
|
||||||
|
if (parts.Length <= 1) return true;
|
||||||
|
|
||||||
|
if (groupSizes is null || groupSizes.Length == 0 || groupSizes[0] == 0)
|
||||||
|
return false; // has groups, but culture defines none.
|
||||||
|
|
||||||
|
var firstPart = parts[0];
|
||||||
|
if (firstPart.StartsWith('-')) firstPart = firstPart[1..];
|
||||||
|
if (firstPart.Length == 0) return false; // e.g. ",123"
|
||||||
|
|
||||||
|
if (firstPart.Length > groupSizes[0]) return false;
|
||||||
|
|
||||||
|
var lastGroupSize = groupSizes.Last();
|
||||||
|
var canRepeatLastGroup = lastGroupSize != 0;
|
||||||
|
|
||||||
|
int groupIndex = 0;
|
||||||
|
for (int i = parts.Length - 1; i > 0; i--)
|
||||||
|
{
|
||||||
|
int expectedSize;
|
||||||
|
if (groupIndex < groupSizes.Length)
|
||||||
|
{
|
||||||
|
expectedSize = groupSizes[groupIndex];
|
||||||
|
}
|
||||||
|
else if(canRepeatLastGroup)
|
||||||
|
{
|
||||||
|
expectedSize = lastGroupSize;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
context.InputDecimalSeparator = Comma;
|
return false;
|
||||||
return numberStr.Replace(Dot, string.Empty).Replace(Comma, Dot);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (parts[i].Length != expectedSize) return false;
|
||||||
|
|
||||||
|
groupIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Case 2: Only dots
|
return true;
|
||||||
if (dotCount > 0)
|
|
||||||
{
|
|
||||||
if (dotCount > 1)
|
|
||||||
{
|
|
||||||
context.InputUsesGroupSeparators = true;
|
|
||||||
return numberStr.Replace(Dot, string.Empty);
|
|
||||||
}
|
|
||||||
// A number is ambiguous if it has a single Dot in the thousands position,
|
|
||||||
// and does not start with a "0." or "."
|
|
||||||
bool isAmbiguous = numberStr.Length - numberStr.LastIndexOf('.') == 4
|
|
||||||
&& !numberStr.StartsWith("0.")
|
|
||||||
&& !numberStr.StartsWith(".");
|
|
||||||
if (isAmbiguous)
|
|
||||||
{
|
|
||||||
if (systemGroupSep == Dot)
|
|
||||||
{
|
|
||||||
context.InputUsesGroupSeparators = true;
|
|
||||||
return numberStr.Replace(Dot, string.Empty);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
context.InputDecimalSeparator = Dot;
|
|
||||||
return numberStr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else // Unambiguous decimal (e.g., "12.34" or "0.123" or ".123")
|
|
||||||
{
|
|
||||||
context.InputDecimalSeparator = Dot;
|
|
||||||
return numberStr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Case 3: Only commas
|
|
||||||
if (commaCount > 0)
|
|
||||||
{
|
|
||||||
if (commaCount > 1)
|
|
||||||
{
|
|
||||||
context.InputUsesGroupSeparators = true;
|
|
||||||
return numberStr.Replace(Comma, string.Empty);
|
|
||||||
}
|
|
||||||
// A number is ambiguous if it has a single Comma in the thousands position,
|
|
||||||
// and does not start with a "0," or ","
|
|
||||||
bool isAmbiguous = numberStr.Length - numberStr.LastIndexOf(',') == 4
|
|
||||||
&& !numberStr.StartsWith("0,")
|
|
||||||
&& !numberStr.StartsWith(",");
|
|
||||||
if (isAmbiguous)
|
|
||||||
{
|
|
||||||
if (systemGroupSep == Comma)
|
|
||||||
{
|
|
||||||
context.InputUsesGroupSeparators = true;
|
|
||||||
return numberStr.Replace(Comma, string.Empty);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
context.InputDecimalSeparator = Comma;
|
|
||||||
return numberStr.Replace(Comma, Dot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else // Unambiguous decimal (e.g., "12,34" or "0,123" or ",123")
|
|
||||||
{
|
|
||||||
context.InputDecimalSeparator = Comma;
|
|
||||||
return numberStr.Replace(Comma, Dot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Case 4: No separators
|
|
||||||
return numberStr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string FormatResult(decimal roundedResult, ParsingContext context)
|
private string FormatResult(decimal roundedResult)
|
||||||
{
|
{
|
||||||
string decimalSeparator = context.InputDecimalSeparator ?? GetDecimalSeparator();
|
string decimalSeparator = GetDecimalSeparator();
|
||||||
string groupSeparator = GetGroupSeparator(decimalSeparator);
|
string groupSeparator = GetGroupSeparator(decimalSeparator);
|
||||||
|
|
||||||
string resultStr = roundedResult.ToString(CultureInfo.InvariantCulture);
|
string resultStr = roundedResult.ToString(CultureInfo.InvariantCulture);
|
||||||
|
|
@ -221,7 +363,7 @@ namespace Flow.Launcher.Plugin.Calculator
|
||||||
string integerPart = parts[0];
|
string integerPart = parts[0];
|
||||||
string fractionalPart = parts.Length > 1 ? parts[1] : string.Empty;
|
string fractionalPart = parts.Length > 1 ? parts[1] : string.Empty;
|
||||||
|
|
||||||
if (context.InputUsesGroupSeparators && integerPart.Length > 3)
|
if (integerPart.Length > 3)
|
||||||
{
|
{
|
||||||
integerPart = ThousandGroupRegex.Replace(integerPart, groupSeparator);
|
integerPart = ThousandGroupRegex.Replace(integerPart, groupSeparator);
|
||||||
}
|
}
|
||||||
|
|
@ -236,29 +378,23 @@ namespace Flow.Launcher.Plugin.Calculator
|
||||||
|
|
||||||
private string GetGroupSeparator(string decimalSeparator)
|
private string GetGroupSeparator(string decimalSeparator)
|
||||||
{
|
{
|
||||||
// This logic is now independent of the system's group separator
|
var culture = CultureInfo.CurrentCulture;
|
||||||
// to ensure consistent output for unit testing.
|
var systemGroupSeparator = culture.NumberFormat.NumberGroupSeparator;
|
||||||
return decimalSeparator == Dot ? Comma : Dot;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool CanCalculate(Query query)
|
if (_settings.DecimalSeparator == DecimalSeparator.UseSystemLocale)
|
||||||
{
|
|
||||||
if (query.Search.Length < 2)
|
|
||||||
{
|
{
|
||||||
return false;
|
return systemGroupSeparator;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!RegValidExpressChar.IsMatch(query.Search))
|
// When a custom decimal separator is used,
|
||||||
|
// use the system's group separator unless it conflicts with the custom decimal separator.
|
||||||
|
if (decimalSeparator == systemGroupSeparator)
|
||||||
{
|
{
|
||||||
return false;
|
// Conflict: use the opposite of the decimal separator as a fallback.
|
||||||
|
return decimalSeparator == Dot ? Comma : Dot;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsBracketComplete(query.Search))
|
return systemGroupSeparator;
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetDecimalSeparator()
|
private string GetDecimalSeparator()
|
||||||
|
|
@ -273,25 +409,6 @@ namespace Flow.Launcher.Plugin.Calculator
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsBracketComplete(string query)
|
|
||||||
{
|
|
||||||
var matchs = RegBrackets.Matches(query);
|
|
||||||
var leftBracketCount = 0;
|
|
||||||
foreach (Match match in matchs)
|
|
||||||
{
|
|
||||||
if (match.Value == "(" || match.Value == "[")
|
|
||||||
{
|
|
||||||
leftBracketCount++;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
leftBracketCount--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return leftBracketCount == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetTranslatedPluginTitle()
|
public string GetTranslatedPluginTitle()
|
||||||
{
|
{
|
||||||
return Localize.flowlauncher_plugin_calculator_plugin_name();
|
return Localize.flowlauncher_plugin_calculator_plugin_name();
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,21 @@ namespace Flow.Launcher.Plugin.Calculator;
|
||||||
|
|
||||||
internal static partial class MainRegexHelper
|
internal static partial class MainRegexHelper
|
||||||
{
|
{
|
||||||
|
[GeneratedRegex(@"-?[\d\.,'\u00A0\u202F]+", RegexOptions.Compiled | RegexOptions.CultureInvariant)]
|
||||||
[GeneratedRegex(@"[\(\)\[\]]", RegexOptions.Compiled)]
|
|
||||||
public static partial Regex GetRegBrackets();
|
|
||||||
|
|
||||||
[GeneratedRegex(@"^(ceil|floor|exp|pi|e|max|min|det|abs|log|ln|sqrt|sin|cos|tan|arcsin|arccos|arctan|eigval|eigvec|eig|sum|polar|plot|round|sort|real|zeta|bin2dec|hex2dec|oct2dec|factorial|sign|isprime|isinfty|==|~=|&&|\|\||(?:\<|\>)=?|[ei]|[0-9]|0x[\da-fA-F]+|[\+\%\-\*\/\^\., ""]|[\(\)\|\!\[\]])+$", RegexOptions.Compiled)]
|
|
||||||
public static partial Regex GetRegValidExpressChar();
|
|
||||||
|
|
||||||
[GeneratedRegex(@"[\d\.,]+", RegexOptions.Compiled)]
|
|
||||||
public static partial Regex GetNumberRegex();
|
public static partial Regex GetNumberRegex();
|
||||||
|
|
||||||
[GeneratedRegex(@"\B(?=(\d{3})+(?!\d))", RegexOptions.Compiled)]
|
[GeneratedRegex(@"\B(?=(\d{3})+(?!\d))", RegexOptions.Compiled)]
|
||||||
public static partial Regex GetThousandGroupRegex();
|
public static partial Regex GetThousandGroupRegex();
|
||||||
|
|
||||||
|
[GeneratedRegex(@"\bpow(\((?:[^()\[\]]|\((?<Depth>)|\)(?<-Depth>)|\[(?<Depth>)|\](?<-Depth>))*(?(Depth)(?!))\))", RegexOptions.Compiled | RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
|
||||||
|
public static partial Regex GetPowRegex();
|
||||||
|
|
||||||
|
[GeneratedRegex(@"\blog(\((?:[^()\[\]]|\((?<Depth>)|\)(?<-Depth>)|\[(?<Depth>)|\](?<-Depth>))*(?(Depth)(?!))\))", RegexOptions.Compiled | RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
|
||||||
|
public static partial Regex GetLogRegex();
|
||||||
|
|
||||||
|
[GeneratedRegex(@"\bln(\((?:[^()\[\]]|\((?<Depth>)|\)(?<-Depth>)|\[(?<Depth>)|\](?<-Depth>))*(?(Depth)(?!))\))", RegexOptions.Compiled | RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
|
||||||
|
public static partial Regex GetLnRegex();
|
||||||
|
|
||||||
|
[GeneratedRegex(@"\b(sqrt|pow|factorial|abs|sign|ceil|floor|round|exp|log|log2|log10|min|max|lt|eq|gt|sin|cos|tan|arcsin|arccos|arctan|isnan|isint|isprime|isinfty|rand|randi|type|is|as|length|throw|catch|eval|map|clamp|lerp|regex|shuffle)\s*\(", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
|
||||||
|
public static partial Regex GetFunctionRegex();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
|
|
||||||
namespace Flow.Launcher.Plugin.Calculator
|
namespace Flow.Launcher.Plugin.Calculator;
|
||||||
|
|
||||||
|
public class Settings
|
||||||
{
|
{
|
||||||
public class Settings
|
public DecimalSeparator DecimalSeparator { get; set; } = DecimalSeparator.UseSystemLocale;
|
||||||
{
|
|
||||||
public DecimalSeparator DecimalSeparator { get; set; } = DecimalSeparator.UseSystemLocale;
|
public int MaxDecimalPlaces { get; set; } = 10;
|
||||||
public int MaxDecimalPlaces { get; set; } = 10;
|
|
||||||
}
|
public bool ShowErrorMessage { get; set; } = false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,25 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace Flow.Launcher.Plugin.Calculator.ViewModels
|
namespace Flow.Launcher.Plugin.Calculator.ViewModels;
|
||||||
|
|
||||||
|
public class SettingsViewModel(Settings settings) : BaseModel
|
||||||
{
|
{
|
||||||
public class SettingsViewModel : BaseModel
|
public Settings Settings { get; } = settings;
|
||||||
|
|
||||||
|
public static IEnumerable<int> MaxDecimalPlacesRange => Enumerable.Range(1, 20);
|
||||||
|
|
||||||
|
public List<DecimalSeparatorLocalized> AllDecimalSeparator { get; } = DecimalSeparatorLocalized.GetValues();
|
||||||
|
|
||||||
|
public DecimalSeparator SelectedDecimalSeparator
|
||||||
{
|
{
|
||||||
public SettingsViewModel(Settings settings)
|
get => Settings.DecimalSeparator;
|
||||||
|
set
|
||||||
{
|
{
|
||||||
Settings = settings;
|
if (Settings.DecimalSeparator != value)
|
||||||
}
|
|
||||||
|
|
||||||
public Settings Settings { get; init; }
|
|
||||||
|
|
||||||
public static IEnumerable<int> MaxDecimalPlacesRange => Enumerable.Range(1, 20);
|
|
||||||
|
|
||||||
public List<DecimalSeparatorLocalized> AllDecimalSeparator { get; } = DecimalSeparatorLocalized.GetValues();
|
|
||||||
|
|
||||||
public DecimalSeparator SelectedDecimalSeparator
|
|
||||||
{
|
|
||||||
get => Settings.DecimalSeparator;
|
|
||||||
set
|
|
||||||
{
|
{
|
||||||
if (Settings.DecimalSeparator != value)
|
Settings.DecimalSeparator = value;
|
||||||
{
|
OnPropertyChanged();
|
||||||
Settings.DecimalSeparator = value;
|
|
||||||
OnPropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="auto" />
|
<RowDefinition Height="auto" />
|
||||||
<RowDefinition Height="auto" />
|
<RowDefinition Height="auto" />
|
||||||
|
<RowDefinition Height="auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
|
|
@ -58,5 +59,14 @@
|
||||||
ItemsSource="{Binding MaxDecimalPlacesRange}"
|
ItemsSource="{Binding MaxDecimalPlacesRange}"
|
||||||
SelectedItem="{Binding Settings.MaxDecimalPlaces}" />
|
SelectedItem="{Binding Settings.MaxDecimalPlaces}" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
Grid.Row="2"
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
Margin="{StaticResource SettingPanelItemTopBottomMargin}"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Content="{DynamicResource flowlauncher_plugin_calculator_show_error_message}"
|
||||||
|
IsChecked="{Binding Settings.ShowErrorMessage, Mode=TwoWay}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,16 @@
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using Flow.Launcher.Plugin.Calculator.ViewModels;
|
using Flow.Launcher.Plugin.Calculator.ViewModels;
|
||||||
|
|
||||||
namespace Flow.Launcher.Plugin.Calculator.Views
|
namespace Flow.Launcher.Plugin.Calculator.Views;
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Interaction logic for CalculatorSettings.xaml
|
|
||||||
/// </summary>
|
|
||||||
public partial class CalculatorSettings : UserControl
|
|
||||||
{
|
|
||||||
private readonly SettingsViewModel _viewModel;
|
|
||||||
private readonly Settings _settings;
|
|
||||||
|
|
||||||
public CalculatorSettings(Settings settings)
|
public partial class CalculatorSettings : UserControl
|
||||||
{
|
{
|
||||||
_viewModel = new SettingsViewModel(settings);
|
private readonly SettingsViewModel _viewModel;
|
||||||
_settings = _viewModel.Settings;
|
|
||||||
DataContext = _viewModel;
|
public CalculatorSettings(Settings settings)
|
||||||
InitializeComponent();
|
{
|
||||||
}
|
_viewModel = new SettingsViewModel(settings);
|
||||||
|
DataContext = _viewModel;
|
||||||
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"ID": "CEA0FDFC6D3B4085823D60DC76F28855",
|
"ID": "CEA0FDFC6D3B4085823D60DC76F28855",
|
||||||
"ActionKeyword": "*",
|
"ActionKeyword": "*",
|
||||||
"Name": "Calculator",
|
"Name": "Calculator",
|
||||||
"Description": "Perform mathematical calculations (including hexadecimal values). Use ',' or '.' as thousand separator or decimal place.",
|
"Description": "Perform mathematical calculations, including hex values and advanced functions such as 'min(1,2,3)', 'sqrt(123)' and 'cos(123)'.",
|
||||||
"Author": "cxfksword, dcog989",
|
"Author": "cxfksword, dcog989",
|
||||||
"Version": "1.0.0",
|
"Version": "1.0.0",
|
||||||
"Language": "csharp",
|
"Language": "csharp",
|
||||||
|
|
|
||||||
|
|
@ -66,8 +66,8 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
{
|
{
|
||||||
contextMenus.Add(new Result
|
contextMenus.Add(new Result
|
||||||
{
|
{
|
||||||
Title = Context.API.GetTranslation("plugin_explorer_add_to_quickaccess_title"),
|
Title = Localize.plugin_explorer_add_to_quickaccess_title(),
|
||||||
SubTitle = Context.API.GetTranslation("plugin_explorer_add_to_quickaccess_subtitle"),
|
SubTitle = Localize.plugin_explorer_add_to_quickaccess_subtitle(),
|
||||||
Action = (context) =>
|
Action = (context) =>
|
||||||
{
|
{
|
||||||
Settings.QuickAccessLinks.Add(new AccessLink
|
Settings.QuickAccessLinks.Add(new AccessLink
|
||||||
|
|
@ -77,16 +77,14 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
Type = record.Type
|
Type = record.Type
|
||||||
});
|
});
|
||||||
|
|
||||||
Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_addfilefoldersuccess"),
|
Context.API.ShowMsg(Localize.plugin_explorer_addfilefoldersuccess(),
|
||||||
Context.API.GetTranslation("plugin_explorer_addfilefoldersuccess_detail"),
|
Localize.plugin_explorer_addfilefoldersuccess_detail(),
|
||||||
Constants.ExplorerIconImageFullPath);
|
Constants.ExplorerIconImageFullPath);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
SubTitleToolTip = Context.API.GetTranslation("plugin_explorer_contextmenu_titletooltip"),
|
SubTitleToolTip = Localize.plugin_explorer_contextmenu_titletooltip(),
|
||||||
TitleToolTip = Context.API.GetTranslation("plugin_explorer_contextmenu_titletooltip"),
|
TitleToolTip = Localize.plugin_explorer_contextmenu_titletooltip(),
|
||||||
IcoPath = Constants.QuickAccessImagePath,
|
IcoPath = Constants.QuickAccessImagePath,
|
||||||
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue718"),
|
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue718"),
|
||||||
});
|
});
|
||||||
|
|
@ -95,22 +93,20 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
{
|
{
|
||||||
contextMenus.Add(new Result
|
contextMenus.Add(new Result
|
||||||
{
|
{
|
||||||
Title = Context.API.GetTranslation("plugin_explorer_remove_from_quickaccess_title"),
|
Title = Localize.plugin_explorer_remove_from_quickaccess_title(),
|
||||||
SubTitle = Context.API.GetTranslation("plugin_explorer_remove_from_quickaccess_subtitle"),
|
SubTitle = Localize.plugin_explorer_remove_from_quickaccess_subtitle(),
|
||||||
Action = (context) =>
|
Action = (context) =>
|
||||||
{
|
{
|
||||||
Settings.QuickAccessLinks.Remove(Settings.QuickAccessLinks.FirstOrDefault(x => string.Equals(x.Path, record.FullPath, StringComparison.OrdinalIgnoreCase)));
|
Settings.QuickAccessLinks.Remove(Settings.QuickAccessLinks.FirstOrDefault(x => string.Equals(x.Path, record.FullPath, StringComparison.OrdinalIgnoreCase)));
|
||||||
|
|
||||||
Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_removefilefoldersuccess"),
|
Context.API.ShowMsg(Localize.plugin_explorer_removefilefoldersuccess(),
|
||||||
Context.API.GetTranslation("plugin_explorer_removefilefoldersuccess_detail"),
|
Localize.plugin_explorer_removefilefoldersuccess_detail(),
|
||||||
Constants.ExplorerIconImageFullPath);
|
Constants.ExplorerIconImageFullPath);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
SubTitleToolTip = Context.API.GetTranslation("plugin_explorer_contextmenu_remove_titletooltip"),
|
SubTitleToolTip = Localize.plugin_explorer_contextmenu_remove_titletooltip(),
|
||||||
TitleToolTip = Context.API.GetTranslation("plugin_explorer_contextmenu_remove_titletooltip"),
|
TitleToolTip = Localize.plugin_explorer_contextmenu_remove_titletooltip(),
|
||||||
IcoPath = Constants.RemoveQuickAccessImagePath,
|
IcoPath = Constants.RemoveQuickAccessImagePath,
|
||||||
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uecc9")
|
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uecc9")
|
||||||
});
|
});
|
||||||
|
|
@ -118,8 +114,8 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
|
|
||||||
contextMenus.Add(new Result
|
contextMenus.Add(new Result
|
||||||
{
|
{
|
||||||
Title = Context.API.GetTranslation("plugin_explorer_copypath"),
|
Title = Localize.plugin_explorer_copypath(),
|
||||||
SubTitle = Context.API.GetTranslation("plugin_explorer_copypath_subtitle"),
|
SubTitle = Localize.plugin_explorer_copypath_subtitle(),
|
||||||
Action = _ =>
|
Action = _ =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -130,7 +126,7 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
LogException("Fail to set text in clipboard", e);
|
LogException("Fail to set text in clipboard", e);
|
||||||
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_explorer_fail_to_set_text"));
|
Context.API.ShowMsgError(Localize.plugin_explorer_fail_to_set_text());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -140,8 +136,8 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
|
|
||||||
contextMenus.Add(new Result
|
contextMenus.Add(new Result
|
||||||
{
|
{
|
||||||
Title = Context.API.GetTranslation("plugin_explorer_copyname"),
|
Title = Localize.plugin_explorer_copyname(),
|
||||||
SubTitle = Context.API.GetTranslation("plugin_explorer_copyname_subtitle"),
|
SubTitle = Localize.plugin_explorer_copyname_subtitle(),
|
||||||
Action = _ =>
|
Action = _ =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -152,7 +148,7 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
LogException("Fail to set text in clipboard", e);
|
LogException("Fail to set text in clipboard", e);
|
||||||
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_explorer_fail_to_set_text"));
|
Context.API.ShowMsgError(Localize.plugin_explorer_fail_to_set_text());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -162,8 +158,8 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
|
|
||||||
contextMenus.Add(new Result
|
contextMenus.Add(new Result
|
||||||
{
|
{
|
||||||
Title = Context.API.GetTranslation("plugin_explorer_copyfilefolder"),
|
Title = Localize.plugin_explorer_copyfilefolder(),
|
||||||
SubTitle = isFile ? Context.API.GetTranslation("plugin_explorer_copyfile_subtitle") : Context.API.GetTranslation("plugin_explorer_copyfolder_subtitle"),
|
SubTitle = isFile ? Localize.plugin_explorer_copyfile_subtitle(): Localize.plugin_explorer_copyfolder_subtitle(),
|
||||||
Action = _ =>
|
Action = _ =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -174,28 +170,26 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
LogException($"Fail to set file/folder in clipboard", e);
|
LogException($"Fail to set file/folder in clipboard", e);
|
||||||
Context.API.ShowMsgError(Context.API.GetTranslation("plugin_explorer_fail_to_set_files"));
|
Context.API.ShowMsgError(Localize.plugin_explorer_fail_to_set_files());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
IcoPath = icoPath,
|
IcoPath = icoPath,
|
||||||
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uf12b")
|
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uf12b")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
if (record.Type is ResultType.File or ResultType.Folder)
|
if (record.Type is ResultType.File or ResultType.Folder)
|
||||||
contextMenus.Add(new Result
|
contextMenus.Add(new Result
|
||||||
{
|
{
|
||||||
Title = Context.API.GetTranslation("plugin_explorer_deletefilefolder"),
|
Title = Localize.plugin_explorer_deletefilefolder(),
|
||||||
SubTitle = isFile ? Context.API.GetTranslation("plugin_explorer_deletefile_subtitle") : Context.API.GetTranslation("plugin_explorer_deletefolder_subtitle"),
|
SubTitle = isFile ? Localize.plugin_explorer_deletefile_subtitle(): Localize.plugin_explorer_deletefolder_subtitle(),
|
||||||
Action = (context) =>
|
Action = (context) =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Context.API.ShowMsgBox(
|
if (Context.API.ShowMsgBox(
|
||||||
string.Format(Context.API.GetTranslation("plugin_explorer_delete_folder_link"), record.FullPath),
|
Localize.plugin_explorer_delete_folder_link(record.FullPath),
|
||||||
Context.API.GetTranslation("plugin_explorer_deletefilefolder"),
|
Localize.plugin_explorer_deletefilefolder(),
|
||||||
MessageBoxButton.OKCancel,
|
MessageBoxButton.OKCancel,
|
||||||
MessageBoxImage.Warning)
|
MessageBoxImage.Warning)
|
||||||
== MessageBoxResult.Cancel)
|
== MessageBoxResult.Cancel)
|
||||||
|
|
@ -208,15 +202,15 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
|
|
||||||
_ = Task.Run(() =>
|
_ = Task.Run(() =>
|
||||||
{
|
{
|
||||||
Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_deletefilefoldersuccess"),
|
Context.API.ShowMsg(Localize.plugin_explorer_deletefilefoldersuccess(),
|
||||||
string.Format(Context.API.GetTranslation("plugin_explorer_deletefilefoldersuccess_detail"), record.FullPath),
|
Localize.plugin_explorer_deletefilefoldersuccess_detail(record.FullPath),
|
||||||
Constants.ExplorerIconImageFullPath);
|
Constants.ExplorerIconImageFullPath);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
LogException($"Fail to delete {record.FullPath}", e);
|
LogException($"Fail to delete {record.FullPath}", e);
|
||||||
Context.API.ShowMsgError(string.Format(Context.API.GetTranslation("plugin_explorer_fail_to_delete"), record.FullPath));
|
Context.API.ShowMsgError(Localize.plugin_explorer_fail_to_delete(record.FullPath));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -230,7 +224,7 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
{
|
{
|
||||||
contextMenus.Add(new Result()
|
contextMenus.Add(new Result()
|
||||||
{
|
{
|
||||||
Title = Context.API.GetTranslation("plugin_explorer_show_contextmenu_title"),
|
Title = Localize.plugin_explorer_show_contextmenu_title(),
|
||||||
IcoPath = Constants.ShowContextMenuImagePath,
|
IcoPath = Constants.ShowContextMenuImagePath,
|
||||||
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue700"),
|
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue700"),
|
||||||
Action = _ =>
|
Action = _ =>
|
||||||
|
|
@ -248,8 +242,8 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
if (record.Type == ResultType.File && CanRunAsDifferentUser(record.FullPath))
|
if (record.Type == ResultType.File && CanRunAsDifferentUser(record.FullPath))
|
||||||
contextMenus.Add(new Result
|
contextMenus.Add(new Result
|
||||||
{
|
{
|
||||||
Title = Context.API.GetTranslation("plugin_explorer_runasdifferentuser"),
|
Title = Localize.plugin_explorer_runasdifferentuser(),
|
||||||
SubTitle = Context.API.GetTranslation("plugin_explorer_runasdifferentuser_subtitle"),
|
SubTitle = Localize.plugin_explorer_runasdifferentuser_subtitle(),
|
||||||
Action = (context) =>
|
Action = (context) =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -259,8 +253,8 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
catch (FileNotFoundException e)
|
catch (FileNotFoundException e)
|
||||||
{
|
{
|
||||||
Context.API.ShowMsgError(
|
Context.API.ShowMsgError(
|
||||||
Context.API.GetTranslation("plugin_explorer_plugin_name"),
|
Localize.plugin_explorer_plugin_name(),
|
||||||
string.Format(Context.API.GetTranslation("plugin_explorer_file_not_found"), e.Message));
|
Localize.plugin_explorer_file_not_found(e.Message));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -317,8 +311,8 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
{
|
{
|
||||||
return new Result
|
return new Result
|
||||||
{
|
{
|
||||||
Title = Context.API.GetTranslation("plugin_explorer_opencontainingfolder"),
|
Title = Localize.plugin_explorer_opencontainingfolder(),
|
||||||
SubTitle = Context.API.GetTranslation("plugin_explorer_opencontainingfolder_subtitle"),
|
SubTitle = Localize.plugin_explorer_opencontainingfolder_subtitle(),
|
||||||
Action = _ =>
|
Action = _ =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -328,7 +322,7 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
LogException($"Fail to open file at {record.FullPath}", e);
|
LogException($"Fail to open file at {record.FullPath}", e);
|
||||||
Context.API.ShowMsgError(string.Format(Context.API.GetTranslation("plugin_explorer_fail_to_open"), record.FullPath));
|
Context.API.ShowMsgError(Localize.plugin_explorer_fail_to_open(record.FullPath));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -339,11 +333,9 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private Result CreateOpenWithEditorResult(SearchResult record, string editorPath)
|
private Result CreateOpenWithEditorResult(SearchResult record, string editorPath)
|
||||||
{
|
{
|
||||||
var name = $"{Context.API.GetTranslation("plugin_explorer_openwitheditor")} {Path.GetFileNameWithoutExtension(editorPath)}";
|
var name = $"{Localize.plugin_explorer_openwitheditor()} {Path.GetFileNameWithoutExtension(editorPath)}";
|
||||||
|
|
||||||
return new Result
|
return new Result
|
||||||
{
|
{
|
||||||
|
|
@ -361,8 +353,7 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
var raw_message = Context.API.GetTranslation("plugin_explorer_openwitheditor_error");
|
var message = Localize.plugin_explorer_openwitheditor_error(record.FullPath, Path.GetFileNameWithoutExtension(editorPath), editorPath);
|
||||||
var message = string.Format(raw_message, record.FullPath, Path.GetFileNameWithoutExtension(editorPath), editorPath);
|
|
||||||
LogException(message, e);
|
LogException(message, e);
|
||||||
Context.API.ShowMsgError(message);
|
Context.API.ShowMsgError(message);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -377,7 +368,7 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
{
|
{
|
||||||
string shellPath = Settings.ShellPath;
|
string shellPath = Settings.ShellPath;
|
||||||
|
|
||||||
var name = $"{Context.API.GetTranslation("plugin_explorer_openwithshell")} {Path.GetFileNameWithoutExtension(shellPath)}";
|
var name = $"{Localize.plugin_explorer_openwithshell()} {Path.GetFileNameWithoutExtension(shellPath)}";
|
||||||
|
|
||||||
return new Result
|
return new Result
|
||||||
{
|
{
|
||||||
|
|
@ -394,8 +385,7 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
var raw_message = Context.API.GetTranslation("plugin_explorer_openwithshell_error");
|
var message = Localize.plugin_explorer_openwithshell_error(record.FullPath, Path.GetFileNameWithoutExtension(shellPath), shellPath);
|
||||||
var message = string.Format(raw_message, record.FullPath, Path.GetFileNameWithoutExtension(shellPath), shellPath);
|
|
||||||
LogException(message, e);
|
LogException(message, e);
|
||||||
Context.API.ShowMsgError(message);
|
Context.API.ShowMsgError(message);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -410,8 +400,8 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
{
|
{
|
||||||
return new Result
|
return new Result
|
||||||
{
|
{
|
||||||
Title = Context.API.GetTranslation("plugin_explorer_excludefromindexsearch"),
|
Title = Localize.plugin_explorer_excludefromindexsearch(),
|
||||||
SubTitle = Context.API.GetTranslation("plugin_explorer_path") + " " + record.FullPath,
|
SubTitle = Localize.plugin_explorer_path()+ " " + record.FullPath,
|
||||||
Action = c_ =>
|
Action = c_ =>
|
||||||
{
|
{
|
||||||
if (!Settings.IndexSearchExcludedSubdirectoryPaths.Any(x => string.Equals(x.Path, record.FullPath, StringComparison.OrdinalIgnoreCase)))
|
if (!Settings.IndexSearchExcludedSubdirectoryPaths.Any(x => string.Equals(x.Path, record.FullPath, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
|
@ -422,8 +412,8 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
|
|
||||||
_ = Task.Run(() =>
|
_ = Task.Run(() =>
|
||||||
{
|
{
|
||||||
Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_excludedfromindexsearch_msg"),
|
Context.API.ShowMsg(Localize.plugin_explorer_excludedfromindexsearch_msg(),
|
||||||
Context.API.GetTranslation("plugin_explorer_path") +
|
Localize.plugin_explorer_path()+
|
||||||
" " + record.FullPath, Constants.ExplorerIconImageFullPath);
|
" " + record.FullPath, Constants.ExplorerIconImageFullPath);
|
||||||
|
|
||||||
// so the new path can be persisted to storage and not wait till next ViewModel save.
|
// so the new path can be persisted to storage and not wait till next ViewModel save.
|
||||||
|
|
@ -441,8 +431,8 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
{
|
{
|
||||||
return new Result
|
return new Result
|
||||||
{
|
{
|
||||||
Title = Context.API.GetTranslation("plugin_explorer_openindexingoptions"),
|
Title = Localize.plugin_explorer_openindexingoptions(),
|
||||||
SubTitle = Context.API.GetTranslation("plugin_explorer_openindexingoptions_subtitle"),
|
SubTitle = Localize.plugin_explorer_openindexingoptions_subtitle(),
|
||||||
Action = _ =>
|
Action = _ =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -459,7 +449,7 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
var message = Context.API.GetTranslation("plugin_explorer_openindexingoptions_errormsg");
|
var message = Localize.plugin_explorer_openindexingoptions_errormsg();
|
||||||
LogException(message, e);
|
LogException(message, e);
|
||||||
Context.API.ShowMsgError(message);
|
Context.API.ShowMsgError(message);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -470,12 +460,12 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result CreateOpenWithMenu(SearchResult record)
|
private static Result CreateOpenWithMenu(SearchResult record)
|
||||||
{
|
{
|
||||||
return new Result
|
return new Result
|
||||||
{
|
{
|
||||||
Title = Context.API.GetTranslation("plugin_explorer_openwith"),
|
Title = Localize.plugin_explorer_openwith(),
|
||||||
SubTitle = Context.API.GetTranslation("plugin_explorer_openwith_subtitle"),
|
SubTitle = Localize.plugin_explorer_openwith_subtitle(),
|
||||||
Action = _ =>
|
Action = _ =>
|
||||||
{
|
{
|
||||||
Process.Start("rundll32.exe", $"{Path.Combine(Environment.SystemDirectory, "shell32.dll")},OpenAs_RunDLL {record.FullPath}");
|
Process.Start("rundll32.exe", $"{Path.Combine(Environment.SystemDirectory, "shell32.dll")},OpenAs_RunDLL {record.FullPath}");
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||||
<PackageReference Include="Droplex" Version="1.7.0" />
|
<PackageReference Include="Droplex" Version="1.7.0" />
|
||||||
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.5" />
|
<PackageReference Include="Flow.Launcher.Localization" Version="0.0.6" />
|
||||||
<PackageReference Include="System.Data.OleDb" Version="9.0.9" />
|
<PackageReference Include="System.Data.OleDb" Version="9.0.9" />
|
||||||
<PackageReference Include="System.Linq.Async" Version="6.0.3" />
|
<PackageReference Include="System.Linq.Async" Version="6.0.3" />
|
||||||
<PackageReference Include="tlbimp-Microsoft.Search.Interop" Version="1.0.0" />
|
<PackageReference Include="tlbimp-Microsoft.Search.Interop" Version="1.0.0" />
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
<system:String x:Key="plugin_explorer_directoryinfosearch_error">Error occurred during search: {0}</system:String>
|
<system:String x:Key="plugin_explorer_directoryinfosearch_error">Error occurred during search: {0}</system:String>
|
||||||
<system:String x:Key="plugin_explorer_opendir_error">Could not open folder</system:String>
|
<system:String x:Key="plugin_explorer_opendir_error">Could not open folder</system:String>
|
||||||
<system:String x:Key="plugin_explorer_openfile_error">Could not open file</system:String>
|
<system:String x:Key="plugin_explorer_openfile_error">Could not open file</system:String>
|
||||||
|
<system:String x:Key="plugin_explorer_new_action_keyword_assigned">This new action keyword is already assigned to another plugin, please choose a different one</system:String>
|
||||||
|
|
||||||
<!-- Controls -->
|
<!-- Controls -->
|
||||||
<system:String x:Key="plugin_explorer_delete">Delete</system:String>
|
<system:String x:Key="plugin_explorer_delete">Delete</system:String>
|
||||||
|
|
|
||||||
|
|
@ -90,12 +90,12 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
|
|
||||||
public string GetTranslatedPluginTitle()
|
public string GetTranslatedPluginTitle()
|
||||||
{
|
{
|
||||||
return Context.API.GetTranslation("plugin_explorer_plugin_name");
|
return Localize.plugin_explorer_plugin_name();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetTranslatedPluginDescription()
|
public string GetTranslatedPluginDescription()
|
||||||
{
|
{
|
||||||
return Context.API.GetTranslation("plugin_explorer_plugin_description");
|
return Localize.plugin_explorer_plugin_description();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnCultureInfoChanged(CultureInfo newCulture)
|
public void OnCultureInfoChanged(CultureInfo newCulture)
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,9 @@ public static class EverythingDownloadHelper
|
||||||
if (string.IsNullOrEmpty(installedLocation))
|
if (string.IsNullOrEmpty(installedLocation))
|
||||||
{
|
{
|
||||||
if (api.ShowMsgBox(
|
if (api.ShowMsgBox(
|
||||||
string.Format(api.GetTranslation("flowlauncher_plugin_everything_installing_select"), Environment.NewLine),
|
Localize.flowlauncher_plugin_everything_installing_select(Environment.NewLine),
|
||||||
api.GetTranslation("flowlauncher_plugin_everything_installing_title"),
|
Localize.flowlauncher_plugin_everything_installing_title(),
|
||||||
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
||||||
{
|
{
|
||||||
var dlg = new System.Windows.Forms.OpenFileDialog
|
var dlg = new System.Windows.Forms.OpenFileDialog
|
||||||
{
|
{
|
||||||
|
|
@ -41,13 +41,13 @@ public static class EverythingDownloadHelper
|
||||||
return installedLocation;
|
return installedLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
api.ShowMsg(api.GetTranslation("flowlauncher_plugin_everything_installing_title"),
|
api.ShowMsg(Localize.flowlauncher_plugin_everything_installing_title(),
|
||||||
api.GetTranslation("flowlauncher_plugin_everything_installing_subtitle"), "", useMainWindowAsOwner: false);
|
Localize.flowlauncher_plugin_everything_installing_subtitle(), "", useMainWindowAsOwner: false);
|
||||||
|
|
||||||
await DroplexPackage.Drop(App.Everything1_4_1_1009).ConfigureAwait(false);
|
await DroplexPackage.Drop(App.Everything1_4_1_1009).ConfigureAwait(false);
|
||||||
|
|
||||||
api.ShowMsg(api.GetTranslation("flowlauncher_plugin_everything_installing_title"),
|
api.ShowMsg(Localize.flowlauncher_plugin_everything_installing_title(),
|
||||||
api.GetTranslation("flowlauncher_plugin_everything_installationsuccess_subtitle"), "", useMainWindowAsOwner: false);
|
Localize.flowlauncher_plugin_everything_installationsuccess_subtitle(), "", useMainWindowAsOwner: false);
|
||||||
|
|
||||||
installedLocation = "C:\\Program Files\\Everything\\Everything.exe";
|
installedLocation = "C:\\Program Files\\Everything\\Everything.exe";
|
||||||
|
|
||||||
|
|
@ -83,6 +83,5 @@ public static class EverythingDownloadHelper
|
||||||
|
|
||||||
var scoopInstalledPath = Environment.ExpandEnvironmentVariables(@"%userprofile%\scoop\apps\everything\current\Everything.exe");
|
var scoopInstalledPath = Environment.ExpandEnvironmentVariables(@"%userprofile%\scoop\apps\everything\current\Everything.exe");
|
||||||
return File.Exists(scoopInstalledPath) ? scoopInstalledPath : string.Empty;
|
return File.Exists(scoopInstalledPath) ? scoopInstalledPath : string.Empty;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
|
||||||
if (!await EverythingApi.IsEverythingRunningAsync(token))
|
if (!await EverythingApi.IsEverythingRunningAsync(token))
|
||||||
throw new EngineNotAvailableException(
|
throw new EngineNotAvailableException(
|
||||||
Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
|
Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
|
||||||
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_click_to_launch_or_install"),
|
Localize.flowlauncher_plugin_everything_click_to_launch_or_install(),
|
||||||
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_is_not_running"),
|
Localize.flowlauncher_plugin_everything_is_not_running(),
|
||||||
Constants.EverythingErrorImagePath,
|
Constants.EverythingErrorImagePath,
|
||||||
ClickToInstallEverythingAsync);
|
ClickToInstallEverythingAsync);
|
||||||
}
|
}
|
||||||
|
|
@ -38,7 +38,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
|
||||||
Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
|
Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
|
||||||
"Please check whether your system is x86 or x64",
|
"Please check whether your system is x86 or x64",
|
||||||
Constants.GeneralSearchErrorImagePath,
|
Constants.GeneralSearchErrorImagePath,
|
||||||
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_sdk_issue"));
|
Localize.flowlauncher_plugin_everything_sdk_issue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,7 +50,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
|
||||||
|
|
||||||
if (installedPath == null)
|
if (installedPath == null)
|
||||||
{
|
{
|
||||||
Main.Context.API.ShowMsgError(Main.Context.API.GetTranslation("flowlauncher_plugin_everything_not_found"));
|
Main.Context.API.ShowMsgError(Localize.flowlauncher_plugin_everything_not_found());
|
||||||
Main.Context.API.LogError(ClassName, "Unable to find Everything.exe");
|
Main.Context.API.LogError(ClassName, "Unable to find Everything.exe");
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -65,7 +65,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
|
||||||
// Just let the user know that Everything is not installed properly and ask them to install it manually
|
// Just let the user know that Everything is not installed properly and ask them to install it manually
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Main.Context.API.ShowMsgError(Main.Context.API.GetTranslation("flowlauncher_plugin_everything_install_issue"));
|
Main.Context.API.ShowMsgError(Localize.flowlauncher_plugin_everything_install_issue());
|
||||||
Main.Context.API.LogException(ClassName, "Failed to install Everything", e);
|
Main.Context.API.LogException(ClassName, "Failed to install Everything", e);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -97,8 +97,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
|
||||||
if (!Settings.EnableEverythingContentSearch)
|
if (!Settings.EnableEverythingContentSearch)
|
||||||
{
|
{
|
||||||
throw new EngineNotAvailableException(Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
|
throw new EngineNotAvailableException(Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
|
||||||
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search"),
|
Localize.flowlauncher_plugin_everything_enable_content_search(),
|
||||||
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search_tips"),
|
Localize.flowlauncher_plugin_everything_enable_content_search_tips(),
|
||||||
Constants.EverythingErrorImagePath,
|
Constants.EverythingErrorImagePath,
|
||||||
_ =>
|
_ =>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -124,7 +124,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Context.API.ShowMsgBox(ex.Message, Context.API.GetTranslation("plugin_explorer_opendir_error"));
|
Context.API.ShowMsgBox(ex.Message, Localize.plugin_explorer_opendir_error());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -138,7 +138,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Context.API.ShowMsgBox(ex.Message, Context.API.GetTranslation("plugin_explorer_opendir_error"));
|
Context.API.ShowMsgBox(ex.Message, Localize.plugin_explorer_opendir_error());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -153,7 +153,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Context.API.ShowMsgBox(ex.Message, Context.API.GetTranslation("plugin_explorer_opendir_error"));
|
Context.API.ShowMsgBox(ex.Message, Localize.plugin_explorer_opendir_error());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -166,7 +166,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
Score = score,
|
Score = score,
|
||||||
TitleToolTip = Main.Context.API.GetTranslation("plugin_explorer_plugin_ToolTipOpenDirectory"),
|
TitleToolTip = Localize.plugin_explorer_plugin_ToolTipOpenDirectory(),
|
||||||
SubTitleToolTip = Settings.DisplayMoreInformationInToolTip ? GetFolderMoreInfoTooltip(path) : path,
|
SubTitleToolTip = Settings.DisplayMoreInformationInToolTip ? GetFolderMoreInfoTooltip(path) : path,
|
||||||
ContextData = new SearchResult { Type = ResultType.Folder, FullPath = path, WindowsIndexed = windowsIndexed }
|
ContextData = new SearchResult { Type = ResultType.Folder, FullPath = path, WindowsIndexed = windowsIndexed }
|
||||||
};
|
};
|
||||||
|
|
@ -190,7 +190,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
||||||
DriveInfo drv = new DriveInfo(driveLetter);
|
DriveInfo drv = new DriveInfo(driveLetter);
|
||||||
var freespace = ToReadableSize(drv.AvailableFreeSpace, 2);
|
var freespace = ToReadableSize(drv.AvailableFreeSpace, 2);
|
||||||
var totalspace = ToReadableSize(drv.TotalSize, 2);
|
var totalspace = ToReadableSize(drv.TotalSize, 2);
|
||||||
var subtitle = string.Format(Context.API.GetTranslation("plugin_explorer_diskfreespace"), freespace, totalspace);
|
var subtitle = Localize.plugin_explorer_diskfreespace(freespace, totalspace);
|
||||||
double usingSize = (Convert.ToDouble(drv.TotalSize) - Convert.ToDouble(drv.AvailableFreeSpace)) / Convert.ToDouble(drv.TotalSize) * 100;
|
double usingSize = (Convert.ToDouble(drv.TotalSize) - Convert.ToDouble(drv.AvailableFreeSpace)) / Convert.ToDouble(drv.TotalSize) * 100;
|
||||||
|
|
||||||
int? progressValue = Convert.ToInt32(usingSize);
|
int? progressValue = Convert.ToInt32(usingSize);
|
||||||
|
|
@ -262,8 +262,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
||||||
|
|
||||||
return new Result
|
return new Result
|
||||||
{
|
{
|
||||||
Title = Context.API.GetTranslation("plugin_explorer_openresultfolder"),
|
Title = Localize.plugin_explorer_openresultfolder(),
|
||||||
SubTitle = Context.API.GetTranslation("plugin_explorer_openresultfolder_subtitle"),
|
SubTitle = Localize.plugin_explorer_openresultfolder_subtitle(),
|
||||||
AutoCompleteText = GetPathWithActionKeyword(folderPath, ResultType.Folder, actionKeyword),
|
AutoCompleteText = GetPathWithActionKeyword(folderPath, ResultType.Folder, actionKeyword),
|
||||||
IcoPath = folderPath,
|
IcoPath = folderPath,
|
||||||
Score = 500,
|
Score = 500,
|
||||||
|
|
@ -330,12 +330,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Context.API.ShowMsgBox(ex.Message, Context.API.GetTranslation("plugin_explorer_openfile_error"));
|
Context.API.ShowMsgBox(ex.Message, Localize.plugin_explorer_openfile_error());
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
TitleToolTip = Main.Context.API.GetTranslation("plugin_explorer_plugin_ToolTipOpenContainingFolder"),
|
TitleToolTip = Localize.plugin_explorer_plugin_ToolTipOpenContainingFolder(),
|
||||||
SubTitleToolTip = Settings.DisplayMoreInformationInToolTip ? GetFileMoreInfoTooltip(filePath) : filePath,
|
SubTitleToolTip = Settings.DisplayMoreInformationInToolTip ? GetFileMoreInfoTooltip(filePath) : filePath,
|
||||||
ContextData = new SearchResult { Type = ResultType.File, FullPath = filePath, WindowsIndexed = windowsIndexed }
|
ContextData = new SearchResult { Type = ResultType.File, FullPath = filePath, WindowsIndexed = windowsIndexed }
|
||||||
};
|
};
|
||||||
|
|
@ -374,8 +374,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
||||||
var fileSize = PreviewPanel.GetFileSize(filePath);
|
var fileSize = PreviewPanel.GetFileSize(filePath);
|
||||||
var fileCreatedAt = PreviewPanel.GetFileCreatedAt(filePath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel);
|
var fileCreatedAt = PreviewPanel.GetFileCreatedAt(filePath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel);
|
||||||
var fileModifiedAt = PreviewPanel.GetFileLastModifiedAt(filePath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel);
|
var fileModifiedAt = PreviewPanel.GetFileLastModifiedAt(filePath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel);
|
||||||
return string.Format(Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info"),
|
return Localize.plugin_explorer_plugin_tooltip_more_info(filePath, fileSize, fileCreatedAt, fileModifiedAt, Environment.NewLine);
|
||||||
filePath, fileSize, fileCreatedAt, fileModifiedAt, Environment.NewLine);
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|
@ -391,8 +390,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
||||||
var folderSize = PreviewPanel.GetFolderSize(folderPath);
|
var folderSize = PreviewPanel.GetFolderSize(folderPath);
|
||||||
var folderCreatedAt = PreviewPanel.GetFolderCreatedAt(folderPath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel);
|
var folderCreatedAt = PreviewPanel.GetFolderCreatedAt(folderPath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel);
|
||||||
var folderModifiedAt = PreviewPanel.GetFolderLastModifiedAt(folderPath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel);
|
var folderModifiedAt = PreviewPanel.GetFolderLastModifiedAt(folderPath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel);
|
||||||
return string.Format(Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info"),
|
return Localize.plugin_explorer_plugin_tooltip_more_info(folderPath, folderSize, folderCreatedAt, folderModifiedAt, Environment.NewLine);
|
||||||
folderPath, folderSize, folderCreatedAt, folderModifiedAt, Environment.NewLine);
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|
@ -403,8 +401,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
||||||
|
|
||||||
private static string GetVolumeMoreInfoTooltip(string volumePath, string freespace, string totalspace)
|
private static string GetVolumeMoreInfoTooltip(string volumePath, string freespace, string totalspace)
|
||||||
{
|
{
|
||||||
return string.Format(Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_volume"),
|
return Localize.plugin_explorer_plugin_tooltip_more_info_volume(volumePath, freespace, totalspace, Environment.NewLine);
|
||||||
volumePath, freespace, totalspace, Environment.NewLine);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly string[] MediaExtensions =
|
private static readonly string[] MediaExtensions =
|
||||||
|
|
|
||||||
|
|
@ -161,8 +161,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
||||||
{
|
{
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Title = Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search"),
|
Title = Localize.flowlauncher_plugin_everything_enable_content_search(),
|
||||||
SubTitle = Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search_tips"),
|
SubTitle = Localize.flowlauncher_plugin_everything_enable_content_search_tips(),
|
||||||
IcoPath = "Images/index_error.png",
|
IcoPath = "Images/index_error.png",
|
||||||
Action = c =>
|
Action = c =>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -105,8 +105,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
||||||
|
|
||||||
throw new EngineNotAvailableException(
|
throw new EngineNotAvailableException(
|
||||||
"Windows Index",
|
"Windows Index",
|
||||||
Main.Context.API.GetTranslation("plugin_explorer_windowsSearchServiceFix"),
|
Localize.plugin_explorer_windowsSearchServiceFix(),
|
||||||
Main.Context.API.GetTranslation("plugin_explorer_windowsSearchServiceNotRunning"),
|
Localize.plugin_explorer_windowsSearchServiceNotRunning(),
|
||||||
Constants.WindowsIndexErrorImagePath,
|
Constants.WindowsIndexErrorImagePath,
|
||||||
c =>
|
c =>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,9 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
{
|
{
|
||||||
public int MaxResult { get; set; } = 100;
|
public int MaxResult { get; set; } = 100;
|
||||||
|
|
||||||
public ObservableCollection<AccessLink> QuickAccessLinks { get; set; } = new();
|
public ObservableCollection<AccessLink> QuickAccessLinks { get; set; } = [];
|
||||||
|
|
||||||
public ObservableCollection<AccessLink> IndexSearchExcludedSubdirectoryPaths { get; set; } = new ObservableCollection<AccessLink>();
|
public ObservableCollection<AccessLink> IndexSearchExcludedSubdirectoryPaths { get; set; } = [];
|
||||||
|
|
||||||
public string EditorPath { get; set; } = "";
|
public string EditorPath { get; set; } = "";
|
||||||
|
|
||||||
|
|
@ -58,7 +58,6 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
|
|
||||||
public bool QuickAccessKeywordEnabled { get; set; }
|
public bool QuickAccessKeywordEnabled { get; set; }
|
||||||
|
|
||||||
|
|
||||||
public bool WarnWindowsSearchServiceOff { get; set; } = true;
|
public bool WarnWindowsSearchServiceOff { get; set; } = true;
|
||||||
|
|
||||||
public bool ShowFileSizeInPreviewPanel { get; set; } = true;
|
public bool ShowFileSizeInPreviewPanel { get; set; } = true;
|
||||||
|
|
@ -69,7 +68,6 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
|
|
||||||
public bool ShowFileAgeInPreviewPanel { get; set; } = false;
|
public bool ShowFileAgeInPreviewPanel { get; set; } = false;
|
||||||
|
|
||||||
|
|
||||||
public string PreviewPanelDateFormat { get; set; } = "yyyy-MM-dd";
|
public string PreviewPanelDateFormat { get; set; } = "yyyy-MM-dd";
|
||||||
|
|
||||||
public string PreviewPanelTimeFormat { get; set; } = "HH:mm";
|
public string PreviewPanelTimeFormat { get; set; } = "HH:mm";
|
||||||
|
|
@ -82,8 +80,8 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
private EverythingSearchManager EverythingManagerInstance => _everythingManagerInstance ??= new EverythingSearchManager(this);
|
private EverythingSearchManager EverythingManagerInstance => _everythingManagerInstance ??= new EverythingSearchManager(this);
|
||||||
private WindowsIndexSearchManager WindowsIndexSearchManager => _windowsIndexSearchManager ??= new WindowsIndexSearchManager(this);
|
private WindowsIndexSearchManager WindowsIndexSearchManager => _windowsIndexSearchManager ??= new WindowsIndexSearchManager(this);
|
||||||
|
|
||||||
|
|
||||||
public IndexSearchEngineOption IndexSearchEngine { get; set; } = IndexSearchEngineOption.WindowsIndex;
|
public IndexSearchEngineOption IndexSearchEngine { get; set; } = IndexSearchEngineOption.WindowsIndex;
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public IIndexProvider IndexProvider => IndexSearchEngine switch
|
public IIndexProvider IndexProvider => IndexSearchEngine switch
|
||||||
{
|
{
|
||||||
|
|
@ -139,7 +137,6 @@ namespace Flow.Launcher.Plugin.Explorer
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
#region Everything Settings
|
#region Everything Settings
|
||||||
|
|
||||||
public string EverythingInstalledPath { get; set; }
|
public string EverythingInstalledPath { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -296,7 +296,7 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var actionKeywordWindow = new ActionKeywordSetting(actionKeyword, Context.API);
|
var actionKeywordWindow = new ActionKeywordSetting(actionKeyword);
|
||||||
|
|
||||||
if (!(actionKeywordWindow.ShowDialog() ?? false))
|
if (!(actionKeywordWindow.ShowDialog() ?? false))
|
||||||
{
|
{
|
||||||
|
|
@ -432,8 +432,8 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
|
||||||
case "QuickAccessLink":
|
case "QuickAccessLink":
|
||||||
if (SelectedQuickAccessLink == null) return;
|
if (SelectedQuickAccessLink == null) return;
|
||||||
if (Context.API.ShowMsgBox(
|
if (Context.API.ShowMsgBox(
|
||||||
Context.API.GetTranslation("plugin_explorer_delete_quick_access_link"),
|
Localize.plugin_explorer_delete_quick_access_link(),
|
||||||
Context.API.GetTranslation("plugin_explorer_delete"),
|
Localize.plugin_explorer_delete(),
|
||||||
MessageBoxButton.OKCancel,
|
MessageBoxButton.OKCancel,
|
||||||
MessageBoxImage.Warning)
|
MessageBoxImage.Warning)
|
||||||
== MessageBoxResult.Cancel)
|
== MessageBoxResult.Cancel)
|
||||||
|
|
@ -443,8 +443,8 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
|
||||||
case "IndexSearchExcludedPaths":
|
case "IndexSearchExcludedPaths":
|
||||||
if (SelectedIndexSearchExcludedPath == null) return;
|
if (SelectedIndexSearchExcludedPath == null) return;
|
||||||
if (Context.API.ShowMsgBox(
|
if (Context.API.ShowMsgBox(
|
||||||
Context.API.GetTranslation("plugin_explorer_delete_index_search_excluded_path"),
|
Localize.plugin_explorer_delete_index_search_excluded_path(),
|
||||||
Context.API.GetTranslation("plugin_explorer_delete"),
|
Localize.plugin_explorer_delete(),
|
||||||
MessageBoxButton.OKCancel,
|
MessageBoxButton.OKCancel,
|
||||||
MessageBoxImage.Warning)
|
MessageBoxImage.Warning)
|
||||||
== MessageBoxResult.Cancel)
|
== MessageBoxResult.Cancel)
|
||||||
|
|
@ -457,7 +457,7 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
|
||||||
|
|
||||||
private void ShowUnselectedMessage()
|
private void ShowUnselectedMessage()
|
||||||
{
|
{
|
||||||
var warning = Context.API.GetTranslation("plugin_explorer_make_selection_warning");
|
var warning = Localize.plugin_explorer_make_selection_warning();
|
||||||
Context.API.ShowMsgBox(warning);
|
Context.API.ShowMsgBox(warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -577,8 +577,8 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int MaxResultLowerLimit => 1;
|
public int MaxResultLowerLimit { get; } = 1;
|
||||||
public int MaxResultUpperLimit => 100000;
|
public int MaxResultUpperLimit { get; } = 100000;
|
||||||
|
|
||||||
public int MaxResult
|
public int MaxResult
|
||||||
{
|
{
|
||||||
|
|
@ -592,7 +592,7 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
|
||||||
|
|
||||||
#region Everything FastSortWarning
|
#region Everything FastSortWarning
|
||||||
|
|
||||||
public List<EverythingSortOptionLocalized> AllEverythingSortOptions = EverythingSortOptionLocalized.GetValues();
|
public List<EverythingSortOptionLocalized> AllEverythingSortOptions { get; } = EverythingSortOptionLocalized.GetValues();
|
||||||
|
|
||||||
public EverythingSortOption SelectedEverythingSortOption
|
public EverythingSortOption SelectedEverythingSortOption
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -29,13 +29,11 @@ namespace Flow.Launcher.Plugin.Explorer.Views
|
||||||
}
|
}
|
||||||
|
|
||||||
private string actionKeyword;
|
private string actionKeyword;
|
||||||
private readonly IPublicAPI _api;
|
|
||||||
private bool _keywordEnabled;
|
private bool _keywordEnabled;
|
||||||
|
|
||||||
public ActionKeywordSetting(ActionKeywordModel selectedActionKeyword, IPublicAPI api)
|
public ActionKeywordSetting(ActionKeywordModel selectedActionKeyword)
|
||||||
{
|
{
|
||||||
CurrentActionKeyword = selectedActionKeyword;
|
CurrentActionKeyword = selectedActionKeyword;
|
||||||
_api = api;
|
|
||||||
ActionKeyword = selectedActionKeyword.Keyword;
|
ActionKeyword = selectedActionKeyword.Keyword;
|
||||||
KeywordEnabled = selectedActionKeyword.Enabled;
|
KeywordEnabled = selectedActionKeyword.Enabled;
|
||||||
|
|
||||||
|
|
@ -60,14 +58,14 @@ namespace Flow.Launcher.Plugin.Explorer.Views
|
||||||
switch (CurrentActionKeyword.KeywordProperty, KeywordEnabled)
|
switch (CurrentActionKeyword.KeywordProperty, KeywordEnabled)
|
||||||
{
|
{
|
||||||
case (Settings.ActionKeyword.FileContentSearchActionKeyword, true):
|
case (Settings.ActionKeyword.FileContentSearchActionKeyword, true):
|
||||||
_api.ShowMsgBox(_api.GetTranslation("plugin_explorer_globalActionKeywordInvalid"));
|
Main.Context.API.ShowMsgBox(Localize.plugin_explorer_globalActionKeywordInvalid());
|
||||||
return;
|
return;
|
||||||
case (Settings.ActionKeyword.QuickAccessActionKeyword, true):
|
case (Settings.ActionKeyword.QuickAccessActionKeyword, true):
|
||||||
_api.ShowMsgBox(_api.GetTranslation("plugin_explorer_quickaccess_globalActionKeywordInvalid"));
|
Main.Context.API.ShowMsgBox(Localize.plugin_explorer_quickaccess_globalActionKeywordInvalid());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!KeywordEnabled || !_api.ActionKeywordAssigned(ActionKeyword))
|
if (!KeywordEnabled || !Main.Context.API.ActionKeywordAssigned(ActionKeyword))
|
||||||
{
|
{
|
||||||
DialogResult = true;
|
DialogResult = true;
|
||||||
Close();
|
Close();
|
||||||
|
|
@ -75,7 +73,7 @@ namespace Flow.Launcher.Plugin.Explorer.Views
|
||||||
}
|
}
|
||||||
|
|
||||||
// The keyword is not valid, so show message
|
// The keyword is not valid, so show message
|
||||||
_api.ShowMsgBox(_api.GetTranslation("newActionKeywordsHasBeenAssigned"));
|
Main.Context.API.ShowMsgBox(Localize.plugin_explorer_new_action_keyword_assigned());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BtnCancel_OnClick(object sender, RoutedEventArgs e)
|
private void BtnCancel_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ public partial class PreviewPanel : UserControl
|
||||||
public string FileName { get; }
|
public string FileName { get; }
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string _fileSize = Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
|
private string _fileSize = Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string _createdAt = "";
|
private string _createdAt = "";
|
||||||
|
|
@ -111,17 +111,17 @@ public partial class PreviewPanel : UserControl
|
||||||
catch (FileNotFoundException)
|
catch (FileNotFoundException)
|
||||||
{
|
{
|
||||||
Main.Context.API.LogError(ClassName, $"File not found: {filePath}");
|
Main.Context.API.LogError(ClassName, $"File not found: {filePath}");
|
||||||
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
|
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
|
||||||
}
|
}
|
||||||
catch (UnauthorizedAccessException)
|
catch (UnauthorizedAccessException)
|
||||||
{
|
{
|
||||||
Main.Context.API.LogError(ClassName, $"Access denied to file: {filePath}");
|
Main.Context.API.LogError(ClassName, $"Access denied to file: {filePath}");
|
||||||
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
|
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Main.Context.API.LogException(ClassName, $"Failed to get file size for {filePath}", e);
|
Main.Context.API.LogException(ClassName, $"Failed to get file size for {filePath}", e);
|
||||||
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
|
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,17 +142,17 @@ public partial class PreviewPanel : UserControl
|
||||||
catch (FileNotFoundException)
|
catch (FileNotFoundException)
|
||||||
{
|
{
|
||||||
Main.Context.API.LogError(ClassName, $"File not found: {filePath}");
|
Main.Context.API.LogError(ClassName, $"File not found: {filePath}");
|
||||||
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
|
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
|
||||||
}
|
}
|
||||||
catch (UnauthorizedAccessException)
|
catch (UnauthorizedAccessException)
|
||||||
{
|
{
|
||||||
Main.Context.API.LogError(ClassName, $"Access denied to file: {filePath}");
|
Main.Context.API.LogError(ClassName, $"Access denied to file: {filePath}");
|
||||||
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
|
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Main.Context.API.LogException(ClassName, $"Failed to get file created date for {filePath}", e);
|
Main.Context.API.LogException(ClassName, $"Failed to get file created date for {filePath}", e);
|
||||||
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
|
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,17 +173,17 @@ public partial class PreviewPanel : UserControl
|
||||||
catch (FileNotFoundException)
|
catch (FileNotFoundException)
|
||||||
{
|
{
|
||||||
Main.Context.API.LogError(ClassName, $"File not found: {filePath}");
|
Main.Context.API.LogError(ClassName, $"File not found: {filePath}");
|
||||||
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
|
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
|
||||||
}
|
}
|
||||||
catch (UnauthorizedAccessException)
|
catch (UnauthorizedAccessException)
|
||||||
{
|
{
|
||||||
Main.Context.API.LogError(ClassName, $"Access denied to file: {filePath}");
|
Main.Context.API.LogError(ClassName, $"Access denied to file: {filePath}");
|
||||||
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
|
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Main.Context.API.LogException(ClassName, $"Failed to get file modified date for {filePath}", e);
|
Main.Context.API.LogException(ClassName, $"Failed to get file modified date for {filePath}", e);
|
||||||
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
|
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -205,17 +205,17 @@ public partial class PreviewPanel : UserControl
|
||||||
catch (FileNotFoundException)
|
catch (FileNotFoundException)
|
||||||
{
|
{
|
||||||
Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}");
|
Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}");
|
||||||
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
|
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
|
||||||
}
|
}
|
||||||
catch (UnauthorizedAccessException)
|
catch (UnauthorizedAccessException)
|
||||||
{
|
{
|
||||||
Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}");
|
Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}");
|
||||||
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
|
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
Main.Context.API.LogError(ClassName, $"Operation timed out while calculating folder size for {folderPath}");
|
Main.Context.API.LogError(ClassName, $"Operation timed out while calculating folder size for {folderPath}");
|
||||||
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
|
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
|
||||||
}
|
}
|
||||||
// For parallel operations, AggregateException may be thrown if any of the tasks fail
|
// For parallel operations, AggregateException may be thrown if any of the tasks fail
|
||||||
catch (AggregateException ae)
|
catch (AggregateException ae)
|
||||||
|
|
@ -224,22 +224,22 @@ public partial class PreviewPanel : UserControl
|
||||||
{
|
{
|
||||||
case FileNotFoundException:
|
case FileNotFoundException:
|
||||||
Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}");
|
Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}");
|
||||||
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
|
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
|
||||||
case UnauthorizedAccessException:
|
case UnauthorizedAccessException:
|
||||||
Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}");
|
Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}");
|
||||||
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
|
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
|
||||||
case OperationCanceledException:
|
case OperationCanceledException:
|
||||||
Main.Context.API.LogError(ClassName, $"Operation timed out while calculating folder size for {folderPath}");
|
Main.Context.API.LogError(ClassName, $"Operation timed out while calculating folder size for {folderPath}");
|
||||||
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
|
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
|
||||||
default:
|
default:
|
||||||
Main.Context.API.LogException(ClassName, $"Failed to get folder size for {folderPath}", ae);
|
Main.Context.API.LogException(ClassName, $"Failed to get folder size for {folderPath}", ae);
|
||||||
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
|
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Main.Context.API.LogException(ClassName, $"Failed to get folder size for {folderPath}", e);
|
Main.Context.API.LogException(ClassName, $"Failed to get folder size for {folderPath}", e);
|
||||||
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
|
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -260,17 +260,17 @@ public partial class PreviewPanel : UserControl
|
||||||
catch (FileNotFoundException)
|
catch (FileNotFoundException)
|
||||||
{
|
{
|
||||||
Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}");
|
Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}");
|
||||||
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
|
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
|
||||||
}
|
}
|
||||||
catch (UnauthorizedAccessException)
|
catch (UnauthorizedAccessException)
|
||||||
{
|
{
|
||||||
Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}");
|
Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}");
|
||||||
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
|
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Main.Context.API.LogException(ClassName, $"Failed to get folder created date for {folderPath}", e);
|
Main.Context.API.LogException(ClassName, $"Failed to get folder created date for {folderPath}", e);
|
||||||
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
|
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -291,17 +291,17 @@ public partial class PreviewPanel : UserControl
|
||||||
catch (FileNotFoundException)
|
catch (FileNotFoundException)
|
||||||
{
|
{
|
||||||
Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}");
|
Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}");
|
||||||
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
|
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
|
||||||
}
|
}
|
||||||
catch (UnauthorizedAccessException)
|
catch (UnauthorizedAccessException)
|
||||||
{
|
{
|
||||||
Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}");
|
Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}");
|
||||||
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
|
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Main.Context.API.LogException(ClassName, $"Failed to get folder modified date for {folderPath}", e);
|
Main.Context.API.LogException(ClassName, $"Failed to get folder modified date for {folderPath}", e);
|
||||||
return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown");
|
return Localize.plugin_explorer_plugin_tooltip_more_info_unknown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -311,21 +311,20 @@ public partial class PreviewPanel : UserControl
|
||||||
var difference = now - fileDateTime;
|
var difference = now - fileDateTime;
|
||||||
|
|
||||||
if (difference.TotalDays < 1)
|
if (difference.TotalDays < 1)
|
||||||
return Main.Context.API.GetTranslation("Today");
|
return Localize.Today();
|
||||||
if (difference.TotalDays < 30)
|
if (difference.TotalDays < 30)
|
||||||
return string.Format(Main.Context.API.GetTranslation("DaysAgo"), (int)difference.TotalDays);
|
return Localize.DaysAgo((int)difference.TotalDays);
|
||||||
|
|
||||||
var monthsDiff = (now.Year - fileDateTime.Year) * 12 + now.Month - fileDateTime.Month;
|
var monthsDiff = (now.Year - fileDateTime.Year) * 12 + now.Month - fileDateTime.Month;
|
||||||
if (monthsDiff == 1)
|
if (monthsDiff == 1)
|
||||||
return Main.Context.API.GetTranslation("OneMonthAgo");
|
return Localize.OneMonthAgo();
|
||||||
if (monthsDiff < 12)
|
if (monthsDiff < 12)
|
||||||
return string.Format(Main.Context.API.GetTranslation("MonthsAgo"), monthsDiff);
|
return Localize.MonthsAgo(monthsDiff);
|
||||||
|
|
||||||
var yearsDiff = now.Year - fileDateTime.Year;
|
var yearsDiff = now.Year - fileDateTime.Year;
|
||||||
if (now.Month < fileDateTime.Month || (now.Month == fileDateTime.Month && now.Day < fileDateTime.Day))
|
if (now.Month < fileDateTime.Month || (now.Month == fileDateTime.Month && now.Day < fileDateTime.Day))
|
||||||
yearsDiff--;
|
yearsDiff--;
|
||||||
|
|
||||||
return yearsDiff == 1 ? Main.Context.API.GetTranslation("OneYearAgo") :
|
return yearsDiff == 1 ? Localize.OneYearAgo(): Localize.YearsAgo(yearsDiff);
|
||||||
string.Format(Main.Context.API.GetTranslation("YearsAgo"), yearsDiff);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ public partial class QuickAccessLinkSettings
|
||||||
// Validate the input before proceeding
|
// Validate the input before proceeding
|
||||||
if (string.IsNullOrEmpty(SelectedName) || string.IsNullOrEmpty(SelectedPath))
|
if (string.IsNullOrEmpty(SelectedName) || string.IsNullOrEmpty(SelectedPath))
|
||||||
{
|
{
|
||||||
var warning = Main.Context.API.GetTranslation("plugin_explorer_quick_access_link_no_folder_selected");
|
var warning = Localize.plugin_explorer_quick_access_link_no_folder_selected();
|
||||||
Main.Context.API.ShowMsgBox(warning);
|
Main.Context.API.ShowMsgBox(warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -107,7 +107,7 @@ public partial class QuickAccessLinkSettings
|
||||||
x.Path.Equals(SelectedPath, StringComparison.OrdinalIgnoreCase) &&
|
x.Path.Equals(SelectedPath, StringComparison.OrdinalIgnoreCase) &&
|
||||||
x.Name.Equals(SelectedName, StringComparison.OrdinalIgnoreCase)))
|
x.Name.Equals(SelectedName, StringComparison.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
var warning = Main.Context.API.GetTranslation("plugin_explorer_quick_access_link_path_already_exists");
|
var warning = Localize.plugin_explorer_quick_access_link_path_already_exists();
|
||||||
Main.Context.API.ShowMsgBox(warning);
|
Main.Context.API.ShowMsgBox(warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ namespace Flow.Launcher.Plugin.Program
|
||||||
|
|
||||||
internal static PluginInitContext Context { get; private set; }
|
internal static PluginInitContext Context { get; private set; }
|
||||||
|
|
||||||
private static readonly List<Result> emptyResults = new();
|
private static readonly List<Result> emptyResults = [];
|
||||||
|
|
||||||
private static readonly MemoryCacheOptions cacheOptions = new() { SizeLimit = 1560 };
|
private static readonly MemoryCacheOptions cacheOptions = new() { SizeLimit = 1560 };
|
||||||
private static MemoryCache cache = new(cacheOptions);
|
private static MemoryCache cache = new(cacheOptions);
|
||||||
|
|
@ -84,7 +84,6 @@ namespace Flow.Launcher.Plugin.Program
|
||||||
{
|
{
|
||||||
await _win32sLock.WaitAsync(token);
|
await _win32sLock.WaitAsync(token);
|
||||||
await _uwpsLock.WaitAsync(token);
|
await _uwpsLock.WaitAsync(token);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Collect all UWP Windows app directories
|
// Collect all UWP Windows app directories
|
||||||
|
|
@ -117,7 +116,7 @@ namespace Flow.Launcher.Plugin.Program
|
||||||
}
|
}
|
||||||
}, token);
|
}, token);
|
||||||
|
|
||||||
resultList = resultList.Any() ? resultList : emptyResults;
|
resultList = resultList.Count != 0 ? resultList : emptyResults;
|
||||||
|
|
||||||
entry.SetSize(resultList.Count);
|
entry.SetSize(resultList.Count);
|
||||||
entry.SetSlidingExpiration(TimeSpan.FromHours(8));
|
entry.SetSlidingExpiration(TimeSpan.FromHours(8));
|
||||||
|
|
@ -250,14 +249,26 @@ namespace Flow.Launcher.Plugin.Program
|
||||||
}
|
}
|
||||||
|
|
||||||
await _win32sLock.WaitAsync();
|
await _win32sLock.WaitAsync();
|
||||||
_win32s = await context.API.LoadCacheBinaryStorageAsync(Win32CacheName, pluginCacheDirectory, new List<Win32>());
|
try
|
||||||
_win32sCount = _win32s.Count;
|
{
|
||||||
_win32sLock.Release();
|
_win32s = await context.API.LoadCacheBinaryStorageAsync(Win32CacheName, pluginCacheDirectory, new List<Win32>());
|
||||||
|
_win32sCount = _win32s.Count;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_win32sLock.Release();
|
||||||
|
}
|
||||||
|
|
||||||
await _uwpsLock.WaitAsync();
|
await _uwpsLock.WaitAsync();
|
||||||
_uwps = await context.API.LoadCacheBinaryStorageAsync(UwpCacheName, pluginCacheDirectory, new List<UWPApp>());
|
try
|
||||||
_uwpsCount = _uwps.Count;
|
{
|
||||||
_uwpsLock.Release();
|
_uwps = await context.API.LoadCacheBinaryStorageAsync(UwpCacheName, pluginCacheDirectory, new List<UWPApp>());
|
||||||
|
_uwpsCount = _uwps.Count;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_uwpsLock.Release();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
Context.API.LogInfo(ClassName, $"Number of preload win32 programs <{_win32sCount}>");
|
Context.API.LogInfo(ClassName, $"Number of preload win32 programs <{_win32sCount}>");
|
||||||
Context.API.LogInfo(ClassName, $"Number of preload uwps <{_uwpsCount}>");
|
Context.API.LogInfo(ClassName, $"Number of preload uwps <{_uwpsCount}>");
|
||||||
|
|
@ -408,38 +419,46 @@ namespace Flow.Launcher.Plugin.Program
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await _uwpsLock.WaitAsync();
|
await _uwpsLock.WaitAsync();
|
||||||
if (_uwps.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
|
var reindexUwps = true;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
|
reindexUwps = _uwps.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier);
|
||||||
var program = _uwps.First(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier);
|
var program = _uwps.First(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier);
|
||||||
program.Enabled = false;
|
program.Enabled = false;
|
||||||
_settings.DisabledProgramSources.Add(new ProgramSource(program));
|
_settings.DisabledProgramSources.Add(new ProgramSource(program));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
_uwpsLock.Release();
|
_uwpsLock.Release();
|
||||||
|
}
|
||||||
|
|
||||||
// Reindex UWP programs
|
// Reindex UWP programs
|
||||||
|
if (reindexUwps)
|
||||||
|
{
|
||||||
_ = Task.Run(IndexUwpProgramsAsync);
|
_ = Task.Run(IndexUwpProgramsAsync);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
_uwpsLock.Release();
|
|
||||||
}
|
|
||||||
|
|
||||||
await _win32sLock.WaitAsync();
|
await _win32sLock.WaitAsync();
|
||||||
if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
|
var reindexWin32s = true;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
|
reindexWin32s = _win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier);
|
||||||
var program = _win32s.First(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier);
|
var program = _win32s.First(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier);
|
||||||
program.Enabled = false;
|
program.Enabled = false;
|
||||||
_settings.DisabledProgramSources.Add(new ProgramSource(program));
|
_settings.DisabledProgramSources.Add(new ProgramSource(program));
|
||||||
_win32sLock.Release();
|
|
||||||
|
|
||||||
// Reindex Win32 programs
|
|
||||||
_ = Task.Run(IndexWin32ProgramsAsync);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
else
|
finally
|
||||||
{
|
{
|
||||||
_win32sLock.Release();
|
_win32sLock.Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reindex Win32 programs
|
||||||
|
if (reindexWin32s)
|
||||||
|
{
|
||||||
|
_ = Task.Run(IndexWin32ProgramsAsync);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void StartProcess(Func<ProcessStartInfo, Process> runProcess, ProcessStartInfo info)
|
public static void StartProcess(Func<ProcessStartInfo, Process> runProcess, ProcessStartInfo info)
|
||||||
|
|
|
||||||
|
|
@ -19,18 +19,30 @@ namespace Flow.Launcher.Plugin.Program.Views.Commands
|
||||||
internal static async Task DisplayAllProgramsAsync()
|
internal static async Task DisplayAllProgramsAsync()
|
||||||
{
|
{
|
||||||
await Main._win32sLock.WaitAsync();
|
await Main._win32sLock.WaitAsync();
|
||||||
var win32 = Main._win32s
|
try
|
||||||
|
{
|
||||||
|
var win32 = Main._win32s
|
||||||
.Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
|
.Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
|
||||||
.Select(x => new ProgramSource(x));
|
.Select(x => new ProgramSource(x));
|
||||||
ProgramSetting.ProgramSettingDisplayList.AddRange(win32);
|
ProgramSetting.ProgramSettingDisplayList.AddRange(win32);
|
||||||
Main._win32sLock.Release();
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Main._win32sLock.Release();
|
||||||
|
}
|
||||||
|
|
||||||
await Main._uwpsLock.WaitAsync();
|
await Main._uwpsLock.WaitAsync();
|
||||||
var uwp = Main._uwps
|
try
|
||||||
|
{
|
||||||
|
var uwp = Main._uwps
|
||||||
.Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
|
.Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
|
||||||
.Select(x => new ProgramSource(x));
|
.Select(x => new ProgramSource(x));
|
||||||
ProgramSetting.ProgramSettingDisplayList.AddRange(uwp);
|
ProgramSetting.ProgramSettingDisplayList.AddRange(uwp);
|
||||||
Main._uwpsLock.Release();
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Main._uwpsLock.Release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static async Task SetProgramSourcesStatusAsync(List<ProgramSource> selectedProgramSourcesToDisable, bool status)
|
internal static async Task SetProgramSourcesStatusAsync(List<ProgramSource> selectedProgramSourcesToDisable, bool status)
|
||||||
|
|
@ -44,24 +56,36 @@ namespace Flow.Launcher.Plugin.Program.Views.Commands
|
||||||
}
|
}
|
||||||
|
|
||||||
await Main._win32sLock.WaitAsync();
|
await Main._win32sLock.WaitAsync();
|
||||||
foreach (var program in Main._win32s)
|
try
|
||||||
{
|
{
|
||||||
if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status))
|
foreach (var program in Main._win32s)
|
||||||
{
|
{
|
||||||
program.Enabled = status;
|
if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status))
|
||||||
|
{
|
||||||
|
program.Enabled = status;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Main._win32sLock.Release();
|
finally
|
||||||
|
{
|
||||||
|
Main._win32sLock.Release();
|
||||||
|
}
|
||||||
|
|
||||||
await Main._uwpsLock.WaitAsync();
|
await Main._uwpsLock.WaitAsync();
|
||||||
foreach (var program in Main._uwps)
|
try
|
||||||
{
|
{
|
||||||
if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status))
|
foreach (var program in Main._uwps)
|
||||||
{
|
{
|
||||||
program.Enabled = status;
|
if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status))
|
||||||
|
{
|
||||||
|
program.Enabled = status;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Main._uwpsLock.Release();
|
finally
|
||||||
|
{
|
||||||
|
Main._uwpsLock.Release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void StoreDisabledInSettings()
|
internal static void StoreDisabledInSettings()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue