Merge branch 'dev' into fix_result_clearing_nonquery

This commit is contained in:
Jeremy Wu 2025-05-19 08:28:52 +10:00 committed by GitHub
commit 2770c6a977
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
85 changed files with 883 additions and 171 deletions

34
.github/workflows/release_deploy.yml vendored Normal file
View file

@ -0,0 +1,34 @@
---
name: New Release Deployments
on:
release:
types: [published]
workflow_dispatch:
jobs:
deploy-website:
runs-on: ubuntu-latest
steps:
- name: Trigger dispatch event for deploying website
run: |
http_status=$(curl -L -f -s -o /dev/null -w "%{http_code}" \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.DEPLOY_FLOW_WEBSITE }}" \
https://api.github.com/repos/Flow-Launcher/flow-launcher.github.io/dispatches \
-d '{"event_type":"deploy"}')
if [ "$http_status" -ne 204 ]; then echo "Error: Deploy website failed, HTTP status code is $http_status"; exit 1; fi
publish-chocolatey:
runs-on: ubuntu-latest
steps:
- name: Trigger dispatch event for publishing to Chocolatey
run: |
http_status=$(curl -L -f -s -o /dev/null -w "%{http_code}" \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.Publish_Chocolatey }}" \
https://api.github.com/repos/Flow-Launcher/chocolatey-package/dispatches \
-d '{"event_type":"publish"}')
if [ "$http_status" -ne 204 ]; then echo "Error: Publish Chocolatey package failed, HTTP status code is $http_status"; exit 1; fi

View file

@ -1,21 +0,0 @@
---
name: Deploy Website On Release
on:
release:
types: [published]
workflow_dispatch:
jobs:
dispatch:
runs-on: ubuntu-latest
steps:
- name: Dispatch event
run: |
http_status=$(curl -L -f -s -o /dev/null -w "%{http_code}" \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.DEPLOY_FLOW_WEBSITE }}" \
https://api.github.com/repos/Flow-Launcher/flow-launcher.github.io/dispatches \
-d '{"event_type":"deploy"}')
if [ "$http_status" -ne 204 ]; then echo "Error: Deploy trigger failed, HTTP status code is $http_status"; exit 1; fi

View file

@ -220,9 +220,19 @@ namespace Flow.Launcher.Core.Plugin
catch (Exception e)
{
API.LogException(ClassName, $"Fail to Init plugin: {pair.Metadata.Name}", e);
pair.Metadata.Disabled = true;
pair.Metadata.HomeDisabled = true;
failedPlugins.Enqueue(pair);
if (pair.Metadata.Disabled && pair.Metadata.HomeDisabled)
{
// If this plugin is already disabled, do not show error message again
// Or else it will be shown every time
API.LogDebug(ClassName, $"Skipped init for <{pair.Metadata.Name}> due to error");
}
else
{
pair.Metadata.Disabled = true;
pair.Metadata.HomeDisabled = true;
failedPlugins.Enqueue(pair);
API.LogDebug(ClassName, $"Disable plugin <{pair.Metadata.Name}> because init failed");
}
}
}));

View file

@ -45,6 +45,22 @@ namespace Flow.Launcher.Infrastructure.Storage
FilesFolders.ValidateDirectory(DirectoryPath);
}
public bool Exists()
{
return File.Exists(FilePath);
}
public void Delete()
{
foreach (var path in new[] { FilePath, BackupFilePath, TempFilePath })
{
if (File.Exists(path))
{
File.Delete(path);
}
}
}
public async Task<T> LoadAsync()
{
if (Data != null)

View file

@ -306,6 +306,10 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public double WindowLeft { get; set; }
public double WindowTop { get; set; }
public double PreviousScreenWidth { get; set; }
public double PreviousScreenHeight { get; set; }
public double PreviousDpiX { get; set; }
public double PreviousDpiY { get; set; }
/// <summary>
/// Custom left position on selected monitor

View file

@ -488,7 +488,7 @@ Jeśli dodasz prefiks '@' podczas wprowadzania skrótu, będzie on pasował do d
<system:String x:Key="update_flowlauncher_update_cancel">Anuluj</system:String>
<system:String x:Key="update_flowlauncher_fail">Aktualizacja nie powiodła się</system:String>
<system:String x:Key="update_flowlauncher_check_connection">Sprawdź połączenie i spróbuj zaktualizować ustawienia proxy do github-cloud.s3.amazonaws.com.</system:String>
<system:String x:Key="update_flowlauncher_update_restart_flowlauncher_tip">Aby dokończyć proces aktualizacji Flow Launcher musi zostać zresetowany</system:String>
<system:String x:Key="update_flowlauncher_update_restart_flowlauncher_tip">Aby dokończyć proces aktualizacji Flow Launcher musi zostać zrestartowany</system:String>
<system:String x:Key="update_flowlauncher_update_update_files">Następujące pliki zostaną zaktualizowane</system:String>
<system:String x:Key="update_flowlauncher_update_files">Aktualizuj pliki</system:String>
<system:String x:Key="update_flowlauncher_update_update_description">Opis aktualizacji</system:String>

View file

@ -22,6 +22,7 @@ using Flow.Launcher.Infrastructure.Image;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.ViewModel;
using Microsoft.Win32;
using ModernWpf.Controls;
using DataObject = System.Windows.DataObject;
using Key = System.Windows.Input.Key;
@ -88,6 +89,8 @@ namespace Flow.Launcher
InitSoundEffects();
DataObject.AddPastingHandler(QueryTextBox, QueryTextBox_OnPaste);
ModernWpf.ThemeManager.Current.ActualApplicationThemeChanged += ThemeManager_ActualApplicationThemeChanged;
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
}
#endregion
@ -96,6 +99,11 @@ namespace Flow.Launcher
#pragma warning disable VSTHRD100 // Avoid async void methods
private void ThemeManager_ActualApplicationThemeChanged(ModernWpf.ThemeManager sender, object args)
{
_theme.RefreshFrameAsync();
}
private void OnSourceInitialized(object sender, EventArgs e)
{
var handle = Win32Helper.GetWindowHandle(this, true);
@ -541,16 +549,29 @@ namespace Flow.Launcher
#region Window Sound Effects
private void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
{
// Fix for sound not playing after sleep / hibernate
// https://stackoverflow.com/questions/64805186/mediaplayer-doesnt-play-after-computer-sleeps
if (e.Mode == PowerModes.Resume)
{
InitSoundEffects();
}
}
private void InitSoundEffects()
{
if (_settings.WMPInstalled)
{
animationSoundWMP?.Close();
animationSoundWMP = new MediaPlayer();
animationSoundWMP.Open(new Uri(AppContext.BaseDirectory + "Resources\\open.wav"));
}
else
{
animationSoundWPF?.Dispose();
animationSoundWPF = new SoundPlayer(AppContext.BaseDirectory + "Resources\\open.wav");
animationSoundWPF.Load();
}
}
@ -696,8 +717,26 @@ namespace Flow.Launcher
{
if (_settings.SearchWindowScreen == SearchWindowScreens.RememberLastLaunchLocation)
{
Top = _settings.WindowTop;
var previousScreenWidth = _settings.PreviousScreenWidth;
var previousScreenHeight = _settings.PreviousScreenHeight;
GetDpi(out var previousDpiX, out var previousDpiY);
_settings.PreviousScreenWidth = SystemParameters.VirtualScreenWidth;
_settings.PreviousScreenHeight = SystemParameters.VirtualScreenHeight;
GetDpi(out var currentDpiX, out var currentDpiY);
if (previousScreenWidth != 0 && previousScreenHeight != 0 &&
previousDpiX != 0 && previousDpiY != 0 &&
(previousScreenWidth != SystemParameters.VirtualScreenWidth ||
previousScreenHeight != SystemParameters.VirtualScreenHeight ||
previousDpiX != currentDpiX || previousDpiY != currentDpiY))
{
AdjustPositionForResolutionChange();
return;
}
Left = _settings.WindowLeft;
Top = _settings.WindowTop;
}
else
{
@ -710,27 +749,73 @@ namespace Flow.Launcher
break;
case SearchWindowAligns.CenterTop:
Left = HorizonCenter(screen);
Top = 10;
Top = VerticalTop(screen);
break;
case SearchWindowAligns.LeftTop:
Left = HorizonLeft(screen);
Top = 10;
Top = VerticalTop(screen);
break;
case SearchWindowAligns.RightTop:
Left = HorizonRight(screen);
Top = 10;
Top = VerticalTop(screen);
break;
case SearchWindowAligns.Custom:
Left = Win32Helper.TransformPixelsToDIP(this,
screen.WorkingArea.X + _settings.CustomWindowLeft, 0).X;
Top = Win32Helper.TransformPixelsToDIP(this, 0,
screen.WorkingArea.Y + _settings.CustomWindowTop).Y;
var customLeft = Win32Helper.TransformPixelsToDIP(this,
screen.WorkingArea.X + _settings.CustomWindowLeft, 0);
var customTop = Win32Helper.TransformPixelsToDIP(this, 0,
screen.WorkingArea.Y + _settings.CustomWindowTop);
Left = customLeft.X;
Top = customTop.Y;
break;
}
}
}
}
private void AdjustPositionForResolutionChange()
{
var screenWidth = SystemParameters.VirtualScreenWidth;
var screenHeight = SystemParameters.VirtualScreenHeight;
GetDpi(out var currentDpiX, out var currentDpiY);
var previousLeft = _settings.WindowLeft;
var previousTop = _settings.WindowTop;
GetDpi(out var previousDpiX, out var previousDpiY);
var widthRatio = screenWidth / _settings.PreviousScreenWidth;
var heightRatio = screenHeight / _settings.PreviousScreenHeight;
var dpiXRatio = currentDpiX / previousDpiX;
var dpiYRatio = currentDpiY / previousDpiY;
var newLeft = previousLeft * widthRatio * dpiXRatio;
var newTop = previousTop * heightRatio * dpiYRatio;
var screenLeft = SystemParameters.VirtualScreenLeft;
var screenTop = SystemParameters.VirtualScreenTop;
var maxX = screenLeft + screenWidth - ActualWidth;
var maxY = screenTop + screenHeight - ActualHeight;
Left = Math.Max(screenLeft, Math.Min(newLeft, maxX));
Top = Math.Max(screenTop, Math.Min(newTop, maxY));
}
private void GetDpi(out double dpiX, out double dpiY)
{
var source = PresentationSource.FromVisual(this);
if (source != null && source.CompositionTarget != null)
{
var matrix = source.CompositionTarget.TransformToDevice;
dpiX = 96 * matrix.M11;
dpiY = 96 * matrix.M22;
}
else
{
dpiX = 96;
dpiY = 96;
}
}
private Screen SelectedScreen()
{
Screen screen;
@ -791,6 +876,13 @@ namespace Flow.Launcher
return left;
}
public double VerticalTop(Screen screen)
{
var dip1 = Win32Helper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y);
var top = dip1.Y + 10;
return top;
}
#endregion
#region Window Animation
@ -817,7 +909,7 @@ namespace Flow.Launcher
{
Name = progressBarAnimationName, Storyboard = progressBarStoryBoard
};
var stopStoryboard = new StopStoryboard()
{
BeginStoryboardName = progressBarAnimationName
@ -838,7 +930,7 @@ namespace Flow.Launcher
progressStyle.Triggers.Add(trigger);
ProgressBar.Style = progressStyle;
_viewModel.ProgressBarVisibility = Visibility.Hidden;
}
@ -886,7 +978,7 @@ namespace Flow.Launcher
Duration = TimeSpan.FromMilliseconds(animationLength),
FillBehavior = FillBehavior.HoldEnd
};
var rightMargin = GetThicknessFromStyle(ClockPanel.Style, new Thickness(0, 0, DefaultRightMargin, 0)).Right;
var thicknessAnimation = new ThicknessAnimation
@ -914,10 +1006,10 @@ namespace Flow.Launcher
clocksb.Children.Add(ClockOpacity);
iconsb.Children.Add(IconMotion);
iconsb.Children.Add(IconOpacity);
_settings.WindowLeft = Left;
_isArrowKeyPressed = false;
clocksb.Begin(ClockPanel);
iconsb.Begin(SearchIcon);
}
@ -1089,7 +1181,7 @@ namespace Flow.Launcher
{
e.Handled = true;
}
#endregion
#region Placeholder
@ -1141,7 +1233,7 @@ namespace Flow.Launcher
}
#endregion
#region Search Delay
private void QueryTextBox_TextChanged1(object sender, TextChangedEventArgs e)
@ -1163,6 +1255,10 @@ namespace Flow.Launcher
{
_hwndSource?.Dispose();
_notifyIcon?.Dispose();
animationSoundWMP?.Close();
animationSoundWPF?.Dispose();
ModernWpf.ThemeManager.Current.ActualApplicationThemeChanged -= ThemeManager_ActualApplicationThemeChanged;
SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
}
_disposed = true;

View file

@ -318,19 +318,37 @@ namespace Flow.Launcher
{
using var explorer = new Process();
var explorerInfo = _settings.CustomExplorer;
var explorerPath = explorerInfo.Path.Trim().ToLowerInvariant();
var targetPath = FileNameOrFilePath is null
? DirectoryPath
: Path.IsPathRooted(FileNameOrFilePath)
? FileNameOrFilePath
: Path.Combine(DirectoryPath, FileNameOrFilePath);
explorer.StartInfo = new ProcessStartInfo
if (Path.GetFileNameWithoutExtension(explorerPath) == "explorer")
{
FileName = explorerInfo.Path.Replace("%d", DirectoryPath),
UseShellExecute = true,
Arguments = FileNameOrFilePath is null
? explorerInfo.DirectoryArgument.Replace("%d", DirectoryPath)
: explorerInfo.FileArgument
.Replace("%d", DirectoryPath)
.Replace("%f",
Path.IsPathRooted(FileNameOrFilePath) ? FileNameOrFilePath : Path.Combine(DirectoryPath, FileNameOrFilePath)
)
};
// Windows File Manager
// We should ignore and pass only the path to Shell to prevent zombie explorer.exe processes
explorer.StartInfo = new ProcessStartInfo
{
FileName = targetPath, // Not explorer, Only path.
UseShellExecute = true // Must be true to open folder
};
}
else
{
// Custom File Manager
explorer.StartInfo = new ProcessStartInfo
{
FileName = explorerInfo.Path.Replace("%d", DirectoryPath),
UseShellExecute = true,
Arguments = FileNameOrFilePath is null
? explorerInfo.DirectoryArgument.Replace("%d", DirectoryPath)
: explorerInfo.FileArgument
.Replace("%d", DirectoryPath)
.Replace("%f", targetPath)
};
}
explorer.Start();
}

View file

@ -9,20 +9,19 @@ namespace Flow.Launcher.Resources.Pages
{
public partial class WelcomePage1
{
public Settings Settings { get; private set; }
private WelcomeViewModel _viewModel;
public Settings Settings { get; } = Ioc.Default.GetRequiredService<Settings>();
private readonly WelcomeViewModel _viewModel = Ioc.Default.GetRequiredService<WelcomeViewModel>();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!IsInitialized)
{
Settings = Ioc.Default.GetRequiredService<Settings>();
_viewModel = Ioc.Default.GetRequiredService<WelcomeViewModel>();
InitializeComponent();
}
// Sometimes the navigation is not triggered by button click,
// so we need to reset the page number
_viewModel.PageNum = 1;
if (!IsInitialized)
{
InitializeComponent();
}
base.OnNavigatedTo(e);
}

View file

@ -11,20 +11,19 @@ namespace Flow.Launcher.Resources.Pages
{
public partial class WelcomePage2
{
public Settings Settings { get; private set; }
private WelcomeViewModel _viewModel;
public Settings Settings { get; } = Ioc.Default.GetRequiredService<Settings>();
private readonly WelcomeViewModel _viewModel = Ioc.Default.GetRequiredService<WelcomeViewModel>();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!IsInitialized)
{
Settings = Ioc.Default.GetRequiredService<Settings>();
_viewModel = Ioc.Default.GetRequiredService<WelcomeViewModel>();
InitializeComponent();
}
// Sometimes the navigation is not triggered by button click,
// so we need to reset the page number
_viewModel.PageNum = 2;
if (!IsInitialized)
{
InitializeComponent();
}
base.OnNavigatedTo(e);
}

View file

@ -7,20 +7,19 @@ namespace Flow.Launcher.Resources.Pages
{
public partial class WelcomePage3
{
public Settings Settings { get; private set; }
private WelcomeViewModel _viewModel;
public Settings Settings { get; } = Ioc.Default.GetRequiredService<Settings>();
private readonly WelcomeViewModel _viewModel = Ioc.Default.GetRequiredService<WelcomeViewModel>();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!IsInitialized)
{
Settings = Ioc.Default.GetRequiredService<Settings>();
_viewModel = Ioc.Default.GetRequiredService<WelcomeViewModel>();
InitializeComponent();
}
// Sometimes the navigation is not triggered by button click,
// so we need to reset the page number
_viewModel.PageNum = 3;
if (!IsInitialized)
{
InitializeComponent();
}
base.OnNavigatedTo(e);
}
}

View file

@ -7,20 +7,19 @@ namespace Flow.Launcher.Resources.Pages
{
public partial class WelcomePage4
{
public Settings Settings { get; private set; }
private WelcomeViewModel _viewModel;
public Settings Settings { get; } = Ioc.Default.GetRequiredService<Settings>();
private readonly WelcomeViewModel _viewModel = Ioc.Default.GetRequiredService<WelcomeViewModel>();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!IsInitialized)
{
Settings = Ioc.Default.GetRequiredService<Settings>();
_viewModel = Ioc.Default.GetRequiredService<WelcomeViewModel>();
InitializeComponent();
}
// Sometimes the navigation is not triggered by button click,
// so we need to reset the page number
_viewModel.PageNum = 4;
if (!IsInitialized)
{
InitializeComponent();
}
base.OnNavigatedTo(e);
}
}

View file

@ -10,20 +10,19 @@ namespace Flow.Launcher.Resources.Pages
{
public partial class WelcomePage5
{
public Settings Settings { get; private set; }
private WelcomeViewModel _viewModel;
public Settings Settings { get; } = Ioc.Default.GetRequiredService<Settings>();
private readonly WelcomeViewModel _viewModel = Ioc.Default.GetRequiredService<WelcomeViewModel>();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!IsInitialized)
{
Settings = Ioc.Default.GetRequiredService<Settings>();
_viewModel = Ioc.Default.GetRequiredService<WelcomeViewModel>();
InitializeComponent();
}
// Sometimes the navigation is not triggered by button click,
// so we need to reset the page number
_viewModel.PageNum = 5;
if (!IsInitialized)
{
InitializeComponent();
}
base.OnNavigatedTo(e);
}

View file

@ -479,7 +479,8 @@ public partial class SettingsPaneThemeViewModel : BaseModel
)
}
};
var vm = new ResultsViewModel(Settings);
// Set main view model to null because the results are for preview only
var vm = new ResultsViewModel(Settings, null);
vm.AddResults(results, "PREVIEW");
PreviewResults = vm;
}

View file

@ -1,19 +1,29 @@
using System.Windows.Navigation;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.SettingPages.ViewModels;
using Flow.Launcher.ViewModel;
namespace Flow.Launcher.SettingPages.Views;
public partial class SettingsPaneAbout
{
private SettingsPaneAboutViewModel _viewModel = null!;
private readonly SettingWindowViewModel _settingViewModel = Ioc.Default.GetRequiredService<SettingWindowViewModel>();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!IsInitialized)
// Sometimes the navigation is not triggered by button click,
// so we need to reset the page type
_settingViewModel.PageType = typeof(SettingsPaneAbout);
// If the navigation is not triggered by button click, view model will be null again
if (_viewModel == null)
{
_viewModel = Ioc.Default.GetRequiredService<SettingsPaneAboutViewModel>();
DataContext = _viewModel;
}
if (!IsInitialized)
{
InitializeComponent();
}
base.OnNavigatedTo(e);

View file

@ -1,19 +1,29 @@
using System.Windows.Navigation;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.SettingPages.ViewModels;
using Flow.Launcher.ViewModel;
namespace Flow.Launcher.SettingPages.Views;
public partial class SettingsPaneGeneral
{
private SettingsPaneGeneralViewModel _viewModel = null!;
private readonly SettingWindowViewModel _settingViewModel = Ioc.Default.GetRequiredService<SettingWindowViewModel>();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!IsInitialized)
// Sometimes the navigation is not triggered by button click,
// so we need to reset the page type
_settingViewModel.PageType = typeof(SettingsPaneGeneral);
// If the navigation is not triggered by button click, view model will be null again
if (_viewModel == null)
{
_viewModel = Ioc.Default.GetRequiredService<SettingsPaneGeneralViewModel>();
DataContext = _viewModel;
}
if (!IsInitialized)
{
InitializeComponent();
}
base.OnNavigatedTo(e);

View file

@ -1,19 +1,29 @@
using System.Windows.Navigation;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.SettingPages.ViewModels;
using Flow.Launcher.ViewModel;
namespace Flow.Launcher.SettingPages.Views;
public partial class SettingsPaneHotkey
{
private SettingsPaneHotkeyViewModel _viewModel = null!;
private readonly SettingWindowViewModel _settingViewModel = Ioc.Default.GetRequiredService<SettingWindowViewModel>();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!IsInitialized)
// Sometimes the navigation is not triggered by button click,
// so we need to reset the page type
_settingViewModel.PageType = typeof(SettingsPaneHotkey);
// If the navigation is not triggered by button click, view model will be null again
if (_viewModel == null)
{
_viewModel = Ioc.Default.GetRequiredService<SettingsPaneHotkeyViewModel>();
DataContext = _viewModel;
}
if (!IsInitialized)
{
InitializeComponent();
}
base.OnNavigatedTo(e);

View file

@ -11,13 +11,22 @@ namespace Flow.Launcher.SettingPages.Views;
public partial class SettingsPanePluginStore
{
private SettingsPanePluginStoreViewModel _viewModel = null!;
private readonly SettingWindowViewModel _settingViewModel = Ioc.Default.GetRequiredService<SettingWindowViewModel>();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!IsInitialized)
// Sometimes the navigation is not triggered by button click,
// so we need to reset the page type
_settingViewModel.PageType = typeof(SettingsPanePluginStore);
// If the navigation is not triggered by button click, view model will be null again
if (_viewModel == null)
{
_viewModel = Ioc.Default.GetRequiredService<SettingsPanePluginStoreViewModel>();
DataContext = _viewModel;
}
if (!IsInitialized)
{
InitializeComponent();
}
_viewModel.PropertyChanged += ViewModel_PropertyChanged;

View file

@ -11,13 +11,22 @@ namespace Flow.Launcher.SettingPages.Views;
public partial class SettingsPanePlugins
{
private SettingsPanePluginsViewModel _viewModel = null!;
private readonly SettingWindowViewModel _settingViewModel = Ioc.Default.GetRequiredService<SettingWindowViewModel>();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!IsInitialized)
// Sometimes the navigation is not triggered by button click,
// so we need to reset the page type
_settingViewModel.PageType = typeof(SettingsPanePlugins);
// If the navigation is not triggered by button click, view model will be null again
if (_viewModel == null)
{
_viewModel = Ioc.Default.GetRequiredService<SettingsPanePluginsViewModel>();
DataContext = _viewModel;
}
if (!IsInitialized)
{
InitializeComponent();
}
_viewModel.PropertyChanged += ViewModel_PropertyChanged;

View file

@ -1,19 +1,29 @@
using System.Windows.Navigation;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.SettingPages.ViewModels;
using Flow.Launcher.ViewModel;
namespace Flow.Launcher.SettingPages.Views;
public partial class SettingsPaneProxy
{
private SettingsPaneProxyViewModel _viewModel = null!;
private readonly SettingWindowViewModel _settingViewModel = Ioc.Default.GetRequiredService<SettingWindowViewModel>();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!IsInitialized)
// Sometimes the navigation is not triggered by button click,
// so we need to reset the page type
_settingViewModel.PageType = typeof(SettingsPaneProxy);
// If the navigation is not triggered by button click, view model will be null again
if (_viewModel == null)
{
_viewModel = Ioc.Default.GetRequiredService<SettingsPaneProxyViewModel>();
DataContext = _viewModel;
}
if (!IsInitialized)
{
InitializeComponent();
}
base.OnNavigatedTo(e);

View file

@ -1,19 +1,29 @@
using System.Windows.Navigation;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.SettingPages.ViewModels;
using Flow.Launcher.ViewModel;
namespace Flow.Launcher.SettingPages.Views;
public partial class SettingsPaneTheme
{
private SettingsPaneThemeViewModel _viewModel = null!;
private readonly SettingWindowViewModel _settingViewModel = Ioc.Default.GetRequiredService<SettingWindowViewModel>();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!IsInitialized)
// Sometimes the navigation is not triggered by button click,
// so we need to reset the page type
_settingViewModel.PageType = typeof(SettingsPaneTheme);
// If the navigation is not triggered by button click, view model will be null again
if (_viewModel == null)
{
_viewModel = Ioc.Default.GetRequiredService<SettingsPaneThemeViewModel>();
DataContext = _viewModel;
}
if (!IsInitialized)
{
InitializeComponent();
}
base.OnNavigatedTo(e);

View file

@ -13,6 +13,7 @@
MinHeight="600"
d:DataContext="{d:DesignInstance vm:SettingWindowViewModel}"
Closed="OnClosed"
FontFamily="{DynamicResource SettingWindowFont}"
Icon="Images\app.ico"
Left="{Binding SettingWindowLeft, Mode=TwoWay}"
Loaded="OnLoaded"

View file

@ -1,4 +1,5 @@
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
@ -18,6 +19,7 @@ public partial class SettingWindow
#region Private Fields
private readonly Settings _settings;
private readonly SettingWindowViewModel _viewModel;
#endregion
@ -26,11 +28,11 @@ public partial class SettingWindow
public SettingWindow()
{
_settings = Ioc.Default.GetRequiredService<Settings>();
var viewModel = Ioc.Default.GetRequiredService<SettingWindowViewModel>();
DataContext = viewModel;
InitializeComponent();
_viewModel = Ioc.Default.GetRequiredService<SettingWindowViewModel>();
DataContext = _viewModel;
// Since WindowStartupLocation is set to Manual, initialize the window position before calling InitializeComponent
UpdatePositionAndState();
InitializeComponent();
}
#endregion
@ -48,10 +50,37 @@ public partial class SettingWindow
hwndTarget.RenderMode = RenderMode.SoftwareOnly; // Must use software only render mode here
UpdatePositionAndState();
_viewModel.PropertyChanged += ViewModel_PropertyChanged;
}
// Sometimes the navigation is not triggered by button click,
// so we need to update the selected item here
private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(SettingWindowViewModel.PageType):
var selectedIndex = _viewModel.PageType.Name switch
{
nameof(SettingsPaneGeneral) => 0,
nameof(SettingsPanePlugins) => 1,
nameof(SettingsPanePluginStore) => 2,
nameof(SettingsPaneTheme) => 3,
nameof(SettingsPaneHotkey) => 4,
nameof(SettingsPaneProxy) => 5,
nameof(SettingsPaneAbout) => 6,
_ => 0
};
NavView.SelectedItem = NavView.MenuItems[selectedIndex];
break;
}
}
private void OnClosed(object sender, EventArgs e)
{
_viewModel.PropertyChanged -= ViewModel_PropertyChanged;
// If app is exiting, settings save is not needed because main window closing event will handle this
if (App.Exiting) return;
// Save settings when window is closed
@ -137,18 +166,40 @@ public partial class SettingWindow
if (previousTop == null || previousLeft == null || !IsPositionValid(previousTop.Value, previousLeft.Value))
{
Top = WindowTop();
Left = WindowLeft();
SetWindowPosition(WindowTop(), WindowLeft());
}
else
{
Top = previousTop.Value;
Left = previousLeft.Value;
var left = _settings.SettingWindowLeft.Value;
var top = _settings.SettingWindowTop.Value;
AdjustWindowPosition(ref top, ref left);
SetWindowPosition(top, left);
}
WindowState = _settings.SettingWindowState;
}
private void SetWindowPosition(double top, double left)
{
// Ensure window does not exceed screen boundaries
top = Math.Max(top, SystemParameters.VirtualScreenTop);
left = Math.Max(left, SystemParameters.VirtualScreenLeft);
top = Math.Min(top, SystemParameters.VirtualScreenHeight - ActualHeight);
left = Math.Min(left, SystemParameters.VirtualScreenWidth - ActualWidth);
Top = top;
Left = left;
}
private void AdjustWindowPosition(ref double top, ref double left)
{
// Adjust window position if it exceeds screen boundaries
top = Math.Max(top, SystemParameters.VirtualScreenTop);
left = Math.Max(left, SystemParameters.VirtualScreenLeft);
top = Math.Min(top, SystemParameters.VirtualScreenHeight - ActualHeight);
left = Math.Min(left, SystemParameters.VirtualScreenWidth - ActualWidth);
}
private static bool IsPositionValid(double top, double left)
{
foreach (var screen in Screen.AllScreens)
@ -190,6 +241,7 @@ public partial class SettingWindow
{
if (args.IsSettingsSelected)
{
_viewModel.SetPageType(typeof(SettingsPaneGeneral));
ContentFrame.Navigate(typeof(SettingsPaneGeneral));
}
else
@ -212,7 +264,11 @@ public partial class SettingWindow
nameof(About) => typeof(SettingsPaneAbout),
_ => typeof(SettingsPaneGeneral)
};
ContentFrame.Navigate(pageType);
// Only navigate if the page type changes to fix navigation forward/back issue
if (_viewModel.SetPageType(pageType))
{
ContentFrame.Navigate(pageType);
}
}
}
@ -230,7 +286,8 @@ public partial class SettingWindow
private void ContentFrame_Loaded(object sender, RoutedEventArgs e)
{
NavView.SelectedItem ??= NavView.MenuItems[0]; /* Set First Page */
_viewModel.SetPageType(null); // Set page type to null so that NavigationView_SelectionChanged can navigate the frame
NavView.SelectedItem = NavView.MenuItems[0]; /* Set First Page */
}
#endregion

View file

@ -1,14 +1,115 @@
using System.Collections.Concurrent;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Storage
{
public class TopMostRecord
public class FlowLauncherJsonStorageTopMostRecord
{
private readonly FlowLauncherJsonStorage<MultipleTopMostRecord> _topMostRecordStorage;
private readonly MultipleTopMostRecord _topMostRecord;
public FlowLauncherJsonStorageTopMostRecord()
{
#pragma warning disable CS0618 // Type or member is obsolete
// Get old data & new data
var topMostRecordStorage = new FlowLauncherJsonStorage<TopMostRecord>();
#pragma warning restore CS0618 // Type or member is obsolete
_topMostRecordStorage = new FlowLauncherJsonStorage<MultipleTopMostRecord>();
// Check if data exist
var oldDataExist = topMostRecordStorage.Exists();
var newDataExist = _topMostRecordStorage.Exists();
// If new data exist, it means we have already migrated the old data
// So we can safely delete the old data and load the new data
if (newDataExist)
{
try
{
topMostRecordStorage.Delete();
}
catch
{
// Ignored - Flow will delete the old data during next startup
}
_topMostRecord = _topMostRecordStorage.Load();
}
// If new data does not exist and old data exist, we need to migrate the old data to the new data
else if (oldDataExist)
{
// Migrate old data to new data
_topMostRecord = _topMostRecordStorage.Load();
var oldTopMostRecord = topMostRecordStorage.Load();
if (oldTopMostRecord == null || oldTopMostRecord.records.IsEmpty) return;
foreach (var record in oldTopMostRecord.records)
{
var newValue = new ConcurrentQueue<Record>();
newValue.Enqueue(record.Value);
_topMostRecord.records.AddOrUpdate(record.Key, newValue, (key, oldValue) =>
{
oldValue.Enqueue(record.Value);
return oldValue;
});
}
// Delete old data and save the new data
try
{
topMostRecordStorage.Delete();
}
catch
{
// Ignored - Flow will delete the old data during next startup
}
Save();
}
// If both data do not exist, we just need to create a new data
else
{
_topMostRecord = _topMostRecordStorage.Load();
}
}
public void Save()
{
_topMostRecordStorage.Save();
}
public bool IsTopMost(Result result)
{
return _topMostRecord.IsTopMost(result);
}
public int GetTopMostIndex(Result result)
{
return _topMostRecord.GetTopMostIndex(result);
}
public void Remove(Result result)
{
_topMostRecord.Remove(result);
}
public void AddOrUpdate(Result result)
{
_topMostRecord.AddOrUpdate(result);
}
}
/// <summary>
/// Old data structure to support only one top most record for the same query
/// </summary>
[Obsolete("Use MultipleTopMostRecord instead. This class will be removed in future versions.")]
internal class TopMostRecord
{
[JsonInclude]
public ConcurrentDictionary<string, Record> records { get; private set; } = new ConcurrentDictionary<string, Record>();
public ConcurrentDictionary<string, Record> records { get; private set; } = new();
internal bool IsTopMost(Result result)
{
@ -39,12 +140,145 @@ namespace Flow.Launcher.Storage
}
}
public class Record
/// <summary>
/// New data structure to support multiple top most records for the same query
/// </summary>
internal class MultipleTopMostRecord
{
public string Title { get; set; }
public string SubTitle { get; set; }
public string PluginID { get; set; }
public string RecordKey { get; set; }
[JsonInclude]
[JsonConverter(typeof(ConcurrentDictionaryConcurrentQueueConverter))]
public ConcurrentDictionary<string, ConcurrentQueue<Record>> records { get; private set; } = new();
internal bool IsTopMost(Result result)
{
// origin query is null when user select the context menu item directly of one item from query list
// in this case, we do not need to check if the result is top most
if (records.IsEmpty || result.OriginQuery == null ||
!records.TryGetValue(result.OriginQuery.RawQuery, out var value))
{
return false;
}
// since this dictionary should be very small (or empty) going over it should be pretty fast.
return value.Any(record => record.Equals(result));
}
internal int GetTopMostIndex(Result result)
{
// origin query is null when user select the context menu item directly of one item from query list
// in this case, we do not need to check if the result is top most
if (records.IsEmpty || result.OriginQuery == null ||
!records.TryGetValue(result.OriginQuery.RawQuery, out var value))
{
return -1;
}
// since this dictionary should be very small (or empty) going over it should be pretty fast.
// since the latter items should be more recent, we should return the smaller index for score to subtract
// which can make them more topmost
// A, B, C => 2, 1, 0 => (max - 2), (max - 1), (max - 0)
var index = 0;
foreach (var record in value)
{
if (record.Equals(result))
{
return value.Count - 1 - index;
}
index++;
}
return -1;
}
internal void Remove(Result result)
{
// origin query is null when user select the context menu item directly of one item from query list
// in this case, we do not need to remove the record
if (result.OriginQuery == null ||
!records.TryGetValue(result.OriginQuery.RawQuery, out var value))
{
return;
}
// remove the record from the queue
var queue = new ConcurrentQueue<Record>(value.Where(r => !r.Equals(result)));
if (queue.IsEmpty)
{
// if the queue is empty, remove the queue from the dictionary
records.TryRemove(result.OriginQuery.RawQuery, out _);
}
else
{
// change the queue in the dictionary
records[result.OriginQuery.RawQuery] = queue;
}
}
internal void AddOrUpdate(Result result)
{
// origin query is null when user select the context menu item directly of one item from query list
// in this case, we do not need to add or update the record
if (result.OriginQuery == null)
{
return;
}
var record = new Record
{
PluginID = result.PluginID,
Title = result.Title,
SubTitle = result.SubTitle,
RecordKey = result.RecordKey
};
if (!records.TryGetValue(result.OriginQuery.RawQuery, out var value))
{
// create a new queue if it does not exist
value = new ConcurrentQueue<Record>();
value.Enqueue(record);
records.TryAdd(result.OriginQuery.RawQuery, value);
}
else
{
// add or update the record in the queue
var queue = new ConcurrentQueue<Record>(value.Where(r => !r.Equals(result))); // make sure we don't have duplicates
queue.Enqueue(record);
records[result.OriginQuery.RawQuery] = queue;
}
}
}
/// <summary>
/// Because ConcurrentQueue does not support serialization, we need to convert it to a List
/// </summary>
internal class ConcurrentDictionaryConcurrentQueueConverter : JsonConverter<ConcurrentDictionary<string, ConcurrentQueue<Record>>>
{
public override ConcurrentDictionary<string, ConcurrentQueue<Record>> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var dictionary = JsonSerializer.Deserialize<Dictionary<string, List<Record>>>(ref reader, options);
var concurrentDictionary = new ConcurrentDictionary<string, ConcurrentQueue<Record>>();
foreach (var kvp in dictionary)
{
concurrentDictionary.TryAdd(kvp.Key, new ConcurrentQueue<Record>(kvp.Value));
}
return concurrentDictionary;
}
public override void Write(Utf8JsonWriter writer, ConcurrentDictionary<string, ConcurrentQueue<Record>> value, JsonSerializerOptions options)
{
var dict = new Dictionary<string, List<Record>>();
foreach (var kvp in value)
{
dict.Add(kvp.Key, kvp.Value.ToList());
}
JsonSerializer.Serialize(writer, dict, options);
}
}
internal class Record
{
public string Title { get; init; }
public string SubTitle { get; init; }
public string PluginID { get; init; }
public string RecordKey { get; init; }
public bool Equals(Result r)
{

View file

@ -39,11 +39,10 @@ namespace Flow.Launcher.ViewModel
private readonly FlowLauncherJsonStorage<History> _historyItemsStorage;
private readonly FlowLauncherJsonStorage<UserSelectedRecord> _userSelectedRecordStorage;
private readonly FlowLauncherJsonStorage<TopMostRecord> _topMostRecordStorage;
private readonly FlowLauncherJsonStorageTopMostRecord _topMostRecord;
private readonly History _history;
private int lastHistoryIndex = 1;
private readonly UserSelectedRecord _userSelectedRecord;
private readonly TopMostRecord _topMostRecord;
private CancellationTokenSource _updateSource; // Used to cancel old query flows
private CancellationToken _updateToken; // Used to avoid ObjectDisposedException of _updateSource.Token
@ -143,24 +142,23 @@ namespace Flow.Launcher.ViewModel
_historyItemsStorage = new FlowLauncherJsonStorage<History>();
_userSelectedRecordStorage = new FlowLauncherJsonStorage<UserSelectedRecord>();
_topMostRecordStorage = new FlowLauncherJsonStorage<TopMostRecord>();
_topMostRecord = new FlowLauncherJsonStorageTopMostRecord();
_history = _historyItemsStorage.Load();
_userSelectedRecord = _userSelectedRecordStorage.Load();
_topMostRecord = _topMostRecordStorage.Load();
ContextMenu = new ResultsViewModel(Settings)
ContextMenu = new ResultsViewModel(Settings, this)
{
LeftClickResultCommand = OpenResultCommand,
RightClickResultCommand = LoadContextMenuCommand,
IsPreviewOn = Settings.AlwaysPreview
};
Results = new ResultsViewModel(Settings)
Results = new ResultsViewModel(Settings, this)
{
LeftClickResultCommand = OpenResultCommand,
RightClickResultCommand = LoadContextMenuCommand,
IsPreviewOn = Settings.AlwaysPreview
};
History = new ResultsViewModel(Settings)
History = new ResultsViewModel(Settings, this)
{
LeftClickResultCommand = OpenResultCommand,
RightClickResultCommand = LoadContextMenuCommand,
@ -1692,6 +1690,12 @@ namespace Flow.Launcher.ViewModel
return selected;
}
internal bool ResultsSelected(ResultsViewModel results)
{
var selected = SelectedResults == results;
return selected;
}
#endregion
#region Hotkey
@ -1847,7 +1851,7 @@ namespace Flow.Launcher.ViewModel
{
_historyItemsStorage.Save();
_userSelectedRecordStorage.Save();
_topMostRecordStorage.Save();
_topMostRecord.Save();
}
/// <summary>
@ -1880,9 +1884,12 @@ namespace Flow.Launcher.ViewModel
{
foreach (var result in metaResults.Results)
{
if (_topMostRecord.IsTopMost(result))
var deviationIndex = _topMostRecord.GetTopMostIndex(result);
if (deviationIndex != -1)
{
result.Score = Result.MaxScore;
// Adjust the score based on the result's position in the top-most list.
// A lower deviationIndex (closer to the top) results in a higher score.
result.Score = Result.MaxScore - deviationIndex;
}
else
{

View file

@ -21,6 +21,7 @@ namespace Flow.Launcher.ViewModel
private readonly object _collectionLock = new();
private readonly Settings _settings;
private readonly MainViewModel _mainVM;
private int MaxResults => _settings?.MaxResultsToShow ?? 6;
public ResultsViewModel()
@ -29,9 +30,10 @@ namespace Flow.Launcher.ViewModel
BindingOperations.EnableCollectionSynchronization(Results, _collectionLock);
}
public ResultsViewModel(Settings settings) : this()
public ResultsViewModel(Settings settings, MainViewModel mainVM) : this()
{
_settings = settings;
_mainVM = mainVM;
_settings.PropertyChanged += (s, e) =>
{
switch (e.PropertyName)
@ -179,6 +181,7 @@ namespace Flow.Launcher.ViewModel
UpdateResults(newResults);
}
/// <summary>
/// To avoid deadlock, this method should not called from main thread
/// </summary>
@ -202,11 +205,18 @@ namespace Flow.Launcher.ViewModel
SelectedItem = Results[0];
}
if (token.IsCancellationRequested)
return;
switch (Visibility)
{
case Visibility.Collapsed when Results.Count > 0:
SelectedIndex = 0;
Visibility = Visibility.Visible;
if (_mainVM == null || // The results are for preview only in appearance page
_mainVM.ResultsSelected(this)) // The results are selected
{
SelectedIndex = 0;
Visibility = Visibility.Visible;
}
break;
case Visibility.Visible when Results.Count == 0:
Visibility = Visibility.Collapsed;

View file

@ -1,4 +1,5 @@
using Flow.Launcher.Infrastructure.UserSettings;
using System;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.ViewModel;
@ -12,6 +13,28 @@ public partial class SettingWindowViewModel : BaseModel
_settings = settings;
}
public bool SetPageType(Type pageType)
{
if (_pageType == pageType) return false;
_pageType = pageType;
return true;
}
private Type _pageType = null;
public Type PageType
{
get => _pageType;
set
{
if (_pageType != value)
{
_pageType = value;
OnPropertyChanged();
}
}
}
public double SettingWindowWidth
{
get => _settings.SettingWindowWidth;

View file

@ -131,7 +131,17 @@ public abstract class ChromiumBookmarkLoader : IBookmarkLoader
}
catch (Exception ex)
{
File.Delete(tempDbPath);
try
{
if (File.Exists(tempDbPath))
{
File.Delete(tempDbPath);
}
}
catch (Exception ex1)
{
Main._context.API.LogException(ClassName, $"Failed to delete temporary favicon DB: {tempDbPath}", ex1);
}
Main._context.API.LogException(ClassName, $"Failed to copy favicon DB: {dbPath}", ex);
return;
}

View file

@ -37,8 +37,6 @@ public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, IContex
context.CurrentPluginMetadata.PluginCacheDirectoryPath,
"FaviconCache");
FilesFolders.ValidateDirectory(_faviconCacheDir);
LoadBookmarksIfEnabled();
}
@ -50,6 +48,9 @@ public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, IContex
return;
}
// Validate the cache directory before loading all bookmarks because Flow needs this directory to storage favicons
FilesFolders.ValidateDirectory(_faviconCacheDir);
_cachedBookmarks = BookmarkLoader.LoadAllBookmarks(_settings);
_ = MonitorRefreshQueueAsync();
_initialized = true;

View file

@ -140,6 +140,29 @@ namespace Flow.Launcher.Plugin.Explorer
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue8c8")
});
contextMenus.Add(new Result
{
Title = Context.API.GetTranslation("plugin_explorer_copyname"),
SubTitle = Context.API.GetTranslation("plugin_explorer_copyname_subtitle"),
Action = _ =>
{
try
{
Context.API.CopyToClipboard(Path.GetFileName(record.FullPath));
return true;
}
catch (Exception e)
{
var message = "Fail to set text in clipboard";
LogException(message, e);
Context.API.ShowMsg(message);
return false;
}
},
IcoPath = Constants.CopyImagePath,
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue8c8")
});
contextMenus.Add(new Result
{
Title = Context.API.GetTranslation("plugin_explorer_copyfilefolder"),

View file

@ -80,6 +80,8 @@
<!-- Context menu items -->
<system:String x:Key="plugin_explorer_copypath">نسخ المسار</system:String>
<system:String x:Key="plugin_explorer_copypath_subtitle">نسخ مسار العنصر الحالي إلى الحافظة</system:String>
<system:String x:Key="plugin_explorer_copyname">Copy name</system:String>
<system:String x:Key="plugin_explorer_copyname_subtitle">Copy name of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfilefolder">نسخ</system:String>
<system:String x:Key="plugin_explorer_copyfile_subtitle">نسخ الملف الحالي إلى الحافظة</system:String>
<system:String x:Key="plugin_explorer_copyfolder_subtitle">نسخ المجلد الحالي إلى الحافظة</system:String>

View file

@ -80,6 +80,8 @@
<!-- Context menu items -->
<system:String x:Key="plugin_explorer_copypath">Kopírovat cestu</system:String>
<system:String x:Key="plugin_explorer_copypath_subtitle">Zkopírovat cestu k aktuální položce do schránky</system:String>
<system:String x:Key="plugin_explorer_copyname">Copy name</system:String>
<system:String x:Key="plugin_explorer_copyname_subtitle">Copy name of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfilefolder">Kopírovat</system:String>
<system:String x:Key="plugin_explorer_copyfile_subtitle">Kopírovat aktuální soubor do schránky</system:String>
<system:String x:Key="plugin_explorer_copyfolder_subtitle">Kopírovat aktuální složku do schránky</system:String>

View file

@ -80,6 +80,8 @@
<!-- Context menu items -->
<system:String x:Key="plugin_explorer_copypath">Copy path</system:String>
<system:String x:Key="plugin_explorer_copypath_subtitle">Copy path of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyname">Copy name</system:String>
<system:String x:Key="plugin_explorer_copyname_subtitle">Copy name of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfilefolder">Copy</system:String>
<system:String x:Key="plugin_explorer_copyfile_subtitle">Copy current file to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfolder_subtitle">Copy current folder to clipboard</system:String>

View file

@ -80,6 +80,8 @@
<!-- Context menu items -->
<system:String x:Key="plugin_explorer_copypath">Pfad kopieren</system:String>
<system:String x:Key="plugin_explorer_copypath_subtitle">Pfad des aktuellen Elements in Zwischenablage kopieren</system:String>
<system:String x:Key="plugin_explorer_copyname">Copy name</system:String>
<system:String x:Key="plugin_explorer_copyname_subtitle">Copy name of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfilefolder">Kopieren</system:String>
<system:String x:Key="plugin_explorer_copyfile_subtitle">Aktuelle Datei in Zwischenablage kopieren</system:String>
<system:String x:Key="plugin_explorer_copyfolder_subtitle">Aktuellen Ordner in Zwischenablage kopieren</system:String>

View file

@ -82,6 +82,8 @@
<!-- Context menu items -->
<system:String x:Key="plugin_explorer_copypath">Copy path</system:String>
<system:String x:Key="plugin_explorer_copypath_subtitle">Copy path of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyname">Copy name</system:String>
<system:String x:Key="plugin_explorer_copyname_subtitle">Copy name of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfilefolder">Copy</system:String>
<system:String x:Key="plugin_explorer_copyfile_subtitle">Copy current file to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfolder_subtitle">Copy current folder to clipboard</system:String>

View file

@ -80,6 +80,8 @@
<!-- Context menu items -->
<system:String x:Key="plugin_explorer_copypath">Copy path</system:String>
<system:String x:Key="plugin_explorer_copypath_subtitle">Copy path of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyname">Copy name</system:String>
<system:String x:Key="plugin_explorer_copyname_subtitle">Copy name of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfilefolder">Copy</system:String>
<system:String x:Key="plugin_explorer_copyfile_subtitle">Copy current file to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfolder_subtitle">Copy current folder to clipboard</system:String>

View file

@ -80,6 +80,8 @@
<!-- Context menu items -->
<system:String x:Key="plugin_explorer_copypath">Copiar ruta</system:String>
<system:String x:Key="plugin_explorer_copypath_subtitle">Copia la ruta del elemento actual al portapapeles</system:String>
<system:String x:Key="plugin_explorer_copyname">Copiar nombre</system:String>
<system:String x:Key="plugin_explorer_copyname_subtitle">Copiar nombre del elemento actual al portapapeles</system:String>
<system:String x:Key="plugin_explorer_copyfilefolder">Copiar</system:String>
<system:String x:Key="plugin_explorer_copyfile_subtitle">Copia el archivo actual al portapapeles</system:String>
<system:String x:Key="plugin_explorer_copyfolder_subtitle">Copia la carpeta actual al portapapeles</system:String>

View file

@ -80,6 +80,8 @@
<!-- Context menu items -->
<system:String x:Key="plugin_explorer_copypath">Copier le chemin</system:String>
<system:String x:Key="plugin_explorer_copypath_subtitle">Copier le chemin de l'élément actuel dans le presse-papiers</system:String>
<system:String x:Key="plugin_explorer_copyname">Copier le nom</system:String>
<system:String x:Key="plugin_explorer_copyname_subtitle">Copier le nom de l'élément actuel dans le presse-papiers</system:String>
<system:String x:Key="plugin_explorer_copyfilefolder">Copier</system:String>
<system:String x:Key="plugin_explorer_copyfile_subtitle">Copier le fichier actuel dans le presse-papiers</system:String>
<system:String x:Key="plugin_explorer_copyfolder_subtitle">Copier le dossier actuel dans le presse-papiers</system:String>

View file

@ -80,6 +80,8 @@
<!-- Context menu items -->
<system:String x:Key="plugin_explorer_copypath">העתק נתיב</system:String>
<system:String x:Key="plugin_explorer_copypath_subtitle">העתק את הנתיב של הפריט הנוכחי ללוח</system:String>
<system:String x:Key="plugin_explorer_copyname">העתק שם</system:String>
<system:String x:Key="plugin_explorer_copyname_subtitle">העתק את שם הפריט הנוכחי ללוח</system:String>
<system:String x:Key="plugin_explorer_copyfilefolder">העתק</system:String>
<system:String x:Key="plugin_explorer_copyfile_subtitle">העתק את הקובץ הנוכחי ללוח</system:String>
<system:String x:Key="plugin_explorer_copyfolder_subtitle">העתק את התיקייה הנוכחית ללוח</system:String>

View file

@ -80,6 +80,8 @@
<!-- Context menu items -->
<system:String x:Key="plugin_explorer_copypath">Copia percorso</system:String>
<system:String x:Key="plugin_explorer_copypath_subtitle">Copia negli appunti il percorso dell'elemento corrente</system:String>
<system:String x:Key="plugin_explorer_copyname">Copy name</system:String>
<system:String x:Key="plugin_explorer_copyname_subtitle">Copy name of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfilefolder">Copia</system:String>
<system:String x:Key="plugin_explorer_copyfile_subtitle">Copia file corrente negli appunti</system:String>
<system:String x:Key="plugin_explorer_copyfolder_subtitle">Copia cartella corrente negli appunti</system:String>

View file

@ -80,6 +80,8 @@
<!-- Context menu items -->
<system:String x:Key="plugin_explorer_copypath">パスをコピー</system:String>
<system:String x:Key="plugin_explorer_copypath_subtitle">現在の項目のパスをコピー</system:String>
<system:String x:Key="plugin_explorer_copyname">Copy name</system:String>
<system:String x:Key="plugin_explorer_copyname_subtitle">Copy name of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfilefolder">コピー</system:String>
<system:String x:Key="plugin_explorer_copyfile_subtitle">現在のファイルをコピー</system:String>
<system:String x:Key="plugin_explorer_copyfolder_subtitle">現在のフォルダーをコピー</system:String>

View file

@ -80,6 +80,8 @@
<!-- Context menu items -->
<system:String x:Key="plugin_explorer_copypath">경로 복사</system:String>
<system:String x:Key="plugin_explorer_copypath_subtitle">이 항목의 경로를 클립보드에 복사</system:String>
<system:String x:Key="plugin_explorer_copyname">Copy name</system:String>
<system:String x:Key="plugin_explorer_copyname_subtitle">Copy name of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfilefolder">복사하기</system:String>
<system:String x:Key="plugin_explorer_copyfile_subtitle">이 파일을 클립보드에 복사</system:String>
<system:String x:Key="plugin_explorer_copyfolder_subtitle">이 파일을 클립보드에 복사</system:String>

View file

@ -80,6 +80,8 @@
<!-- Context menu items -->
<system:String x:Key="plugin_explorer_copypath">Kopier sti</system:String>
<system:String x:Key="plugin_explorer_copypath_subtitle">Kopier sti til gjeldende element til utklippstavlen</system:String>
<system:String x:Key="plugin_explorer_copyname">Copy name</system:String>
<system:String x:Key="plugin_explorer_copyname_subtitle">Copy name of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfilefolder">Kopier</system:String>
<system:String x:Key="plugin_explorer_copyfile_subtitle">Kopier gjeldende fil til utklippstavlen</system:String>
<system:String x:Key="plugin_explorer_copyfolder_subtitle">Kopier gjeldende mappe til utklippstavlen</system:String>

View file

@ -80,6 +80,8 @@
<!-- Context menu items -->
<system:String x:Key="plugin_explorer_copypath">Copy path</system:String>
<system:String x:Key="plugin_explorer_copypath_subtitle">Copy path of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyname">Copy name</system:String>
<system:String x:Key="plugin_explorer_copyname_subtitle">Copy name of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfilefolder">Copy</system:String>
<system:String x:Key="plugin_explorer_copyfile_subtitle">Copy current file to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfolder_subtitle">Copy current folder to clipboard</system:String>

View file

@ -80,6 +80,8 @@
<!-- Context menu items -->
<system:String x:Key="plugin_explorer_copypath">Skopiuj Ścieżkę</system:String>
<system:String x:Key="plugin_explorer_copypath_subtitle">Kopiuj ścieżkę bieżącego elementu do schowka</system:String>
<system:String x:Key="plugin_explorer_copyname">Copy name</system:String>
<system:String x:Key="plugin_explorer_copyname_subtitle">Copy name of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfilefolder">Kopiuj</system:String>
<system:String x:Key="plugin_explorer_copyfile_subtitle">Kopiuj bieżący plik do schowka</system:String>
<system:String x:Key="plugin_explorer_copyfolder_subtitle">Kopiuj bieżący folder do schowka</system:String>

View file

@ -80,6 +80,8 @@
<!-- Context menu items -->
<system:String x:Key="plugin_explorer_copypath">Copy path</system:String>
<system:String x:Key="plugin_explorer_copypath_subtitle">Copy path of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyname">Copy name</system:String>
<system:String x:Key="plugin_explorer_copyname_subtitle">Copy name of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfilefolder">Copiar</system:String>
<system:String x:Key="plugin_explorer_copyfile_subtitle">Copy current file to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfolder_subtitle">Copy current folder to clipboard</system:String>

View file

@ -80,6 +80,8 @@
<!-- Context menu items -->
<system:String x:Key="plugin_explorer_copypath">Copiar caminho</system:String>
<system:String x:Key="plugin_explorer_copypath_subtitle">Copiar caminho do item atual para área de transferência</system:String>
<system:String x:Key="plugin_explorer_copyname">Copiar nome</system:String>
<system:String x:Key="plugin_explorer_copyname_subtitle">Copiar nome do item para a área de transferência</system:String>
<system:String x:Key="plugin_explorer_copyfilefolder">Copiar</system:String>
<system:String x:Key="plugin_explorer_copyfile_subtitle">Copiar ficheiro atual para a área de transferência</system:String>
<system:String x:Key="plugin_explorer_copyfolder_subtitle">Copiar pasta atual para a área de transferência</system:String>

View file

@ -80,6 +80,8 @@
<!-- Context menu items -->
<system:String x:Key="plugin_explorer_copypath">Скопировать путь</system:String>
<system:String x:Key="plugin_explorer_copypath_subtitle">Copy path of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyname">Copy name</system:String>
<system:String x:Key="plugin_explorer_copyname_subtitle">Copy name of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfilefolder">Скопировать</system:String>
<system:String x:Key="plugin_explorer_copyfile_subtitle">Copy current file to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfolder_subtitle">Copy current folder to clipboard</system:String>

View file

@ -80,6 +80,8 @@
<!-- Context menu items -->
<system:String x:Key="plugin_explorer_copypath">Kopírovať cestu</system:String>
<system:String x:Key="plugin_explorer_copypath_subtitle">Skopírovať cestu k aktuálnej položke do schránky</system:String>
<system:String x:Key="plugin_explorer_copyname">Kopírovať názov</system:String>
<system:String x:Key="plugin_explorer_copyname_subtitle">Skopírovať názov aktuálnej položky do schránky</system:String>
<system:String x:Key="plugin_explorer_copyfilefolder">Kopírovať</system:String>
<system:String x:Key="plugin_explorer_copyfile_subtitle">Skopírovať aktuálny súbor do schránky</system:String>
<system:String x:Key="plugin_explorer_copyfolder_subtitle">Skopírovať aktuálny priečinok do schránky</system:String>

View file

@ -80,6 +80,8 @@
<!-- Context menu items -->
<system:String x:Key="plugin_explorer_copypath">Copy path</system:String>
<system:String x:Key="plugin_explorer_copypath_subtitle">Copy path of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyname">Copy name</system:String>
<system:String x:Key="plugin_explorer_copyname_subtitle">Copy name of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfilefolder">Copy</system:String>
<system:String x:Key="plugin_explorer_copyfile_subtitle">Copy current file to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfolder_subtitle">Copy current folder to clipboard</system:String>

View file

@ -80,6 +80,8 @@
<!-- Context menu items -->
<system:String x:Key="plugin_explorer_copypath">Yolu kopyala</system:String>
<system:String x:Key="plugin_explorer_copypath_subtitle">Mevcut dosya konumunu panoya kopyala</system:String>
<system:String x:Key="plugin_explorer_copyname">Copy name</system:String>
<system:String x:Key="plugin_explorer_copyname_subtitle">Copy name of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfilefolder">Kopyala</system:String>
<system:String x:Key="plugin_explorer_copyfile_subtitle">Mevcut dosyayı panoya kopyala</system:String>
<system:String x:Key="plugin_explorer_copyfolder_subtitle">Mevcut klasörü panoya kopyala</system:String>

View file

@ -80,6 +80,8 @@
<!-- Context menu items -->
<system:String x:Key="plugin_explorer_copypath">Копіювати шлях</system:String>
<system:String x:Key="plugin_explorer_copypath_subtitle">Копіювати шлях до поточного елемента в буфер обміну</system:String>
<system:String x:Key="plugin_explorer_copyname">Copy name</system:String>
<system:String x:Key="plugin_explorer_copyname_subtitle">Copy name of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfilefolder">Копіювати</system:String>
<system:String x:Key="plugin_explorer_copyfile_subtitle">Копіювання поточного файлу в буфер обміну</system:String>
<system:String x:Key="plugin_explorer_copyfolder_subtitle">Копіювати поточну папку в буфер обміну</system:String>
@ -161,6 +163,5 @@
<system:String x:Key="plugin_explorer_native_context_menu_header">Рідне контекстне меню</system:String>
<system:String x:Key="plugin_explorer_native_context_menu_display_context_menu">Відображати рідне контекстне меню (експериментально)</system:String>
<system:String x:Key="plugin_explorer_native_context_menu_include_patterns_guide">Нижче ви можете вказати елементи, які хочете включити до контекстного меню, вони можуть бути частковими (наприклад, «шир пера») або повними («Відкрити за допомогою»).</system:String>
<system:String x:Key="plugin_explorer_native_context_menu_exclude_patterns_guide">Нижче ви можете вказати елементи, які хочете виключити з контекстного меню, вони можуть бути частковими (наприклад, «шир пера») або повними («Відкрити за допомогою»).</system:String>
<system:String x:Key="plugin_explorer_native_context_menu_exclude_patterns_guide">Below you can specify items you want to exclude from context menu, they can be partial (e.g. 'pen wit') or complete ('Open with').</system:String>
</ResourceDictionary>

View file

@ -80,6 +80,8 @@
<!-- Context menu items -->
<system:String x:Key="plugin_explorer_copypath">Copy đường dẫn</system:String>
<system:String x:Key="plugin_explorer_copypath_subtitle">Sao chép đường dẫn của mục hiện tại vào clipboard</system:String>
<system:String x:Key="plugin_explorer_copyname">Copy name</system:String>
<system:String x:Key="plugin_explorer_copyname_subtitle">Copy name of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfilefolder">Sao chép</system:String>
<system:String x:Key="plugin_explorer_copyfile_subtitle">Sao chép tập tin hiện tại vào clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfolder_subtitle">Copy current folder to clipboard</system:String>

View file

@ -80,6 +80,8 @@
<!-- Context menu items -->
<system:String x:Key="plugin_explorer_copypath">复制路径</system:String>
<system:String x:Key="plugin_explorer_copypath_subtitle">复制当前结果的路径到剪贴板</system:String>
<system:String x:Key="plugin_explorer_copyname">Copy name</system:String>
<system:String x:Key="plugin_explorer_copyname_subtitle">Copy name of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfilefolder">复制</system:String>
<system:String x:Key="plugin_explorer_copyfile_subtitle">复制当前文件到剪贴板</system:String>
<system:String x:Key="plugin_explorer_copyfolder_subtitle">复制当前文件夹到剪贴板</system:String>

View file

@ -80,6 +80,8 @@
<!-- Context menu items -->
<system:String x:Key="plugin_explorer_copypath">複製路徑</system:String>
<system:String x:Key="plugin_explorer_copypath_subtitle">Copy path of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyname">Copy name</system:String>
<system:String x:Key="plugin_explorer_copyname_subtitle">Copy name of current item to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfilefolder">複製</system:String>
<system:String x:Key="plugin_explorer_copyfile_subtitle">Copy current file to clipboard</system:String>
<system:String x:Key="plugin_explorer_copyfolder_subtitle">Copy current folder to clipboard</system:String>

View file

@ -8,7 +8,6 @@ using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using Flow.Launcher.Plugin.Explorer.Exceptions;

View file

@ -10,6 +10,6 @@
public bool WarnFromUnknownSource { get; set; } = true;
public bool AutoRestartAfterChanging { get; set; } = true;
public bool AutoRestartAfterChanging { get; set; } = false;
}
}

View file

@ -46,6 +46,8 @@
<system:String x:Key="flowlauncher_plugin_program_pls_select_program_source">الرجاء تحديد مصدر برنامج</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source">هل أنت متأكد أنك تريد حذف مصادر البرامج المحددة؟</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_not_user_added">Please select program sources that are not added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_user_added">Please select program sources that are added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_duplicate_program_source">مصدر برنامج آخر بنفس الموقع موجود بالفعل.</system:String>
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">مصدر البرنامج</system:String>

View file

@ -46,6 +46,8 @@
<system:String x:Key="flowlauncher_plugin_program_pls_select_program_source">Prosím vyberte zdroj programu</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source">Jste si jisti, že chcete odstranit vybrané zdroje programů?</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_not_user_added">Please select program sources that are not added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_user_added">Please select program sources that are added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_duplicate_program_source">Již existuje jiný zdroj programu se stejným umístěním.</system:String>
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">Zdroj programu</system:String>

View file

@ -46,6 +46,8 @@
<system:String x:Key="flowlauncher_plugin_program_pls_select_program_source">Please select a program source</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source">Are you sure you want to delete the selected program sources?</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_not_user_added">Please select program sources that are not added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_user_added">Please select program sources that are added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_duplicate_program_source">Another program source with the same location already exists.</system:String>
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">Program Source</system:String>

View file

@ -46,6 +46,8 @@
<system:String x:Key="flowlauncher_plugin_program_pls_select_program_source">Bitte wählen Sie eine Programmquelle aus</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source">Sind Sie sicher, dass Sie die ausgewählten Programmquellen löschen wollen?</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_not_user_added">Please select program sources that are not added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_user_added">Please select program sources that are added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_duplicate_program_source">Eine andere Programmquelle mit dem gleichen Ort ist bereits vorhanden.</system:String>
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">Programmquelle</system:String>

View file

@ -48,6 +48,8 @@
<system:String x:Key="flowlauncher_plugin_program_pls_select_program_source">Please select a program source</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source">Are you sure you want to delete the selected program sources?</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_not_user_added">Please select program sources that are not added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_user_added">Please select program sources that are added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_duplicate_program_source">Another program source with the same location already exists.</system:String>
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">Program Source</system:String>

View file

@ -46,6 +46,8 @@
<system:String x:Key="flowlauncher_plugin_program_pls_select_program_source">Please select a program source</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source">Are you sure you want to delete the selected program sources?</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_not_user_added">Please select program sources that are not added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_user_added">Please select program sources that are added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_duplicate_program_source">Another program source with the same location already exists.</system:String>
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">Program Source</system:String>

View file

@ -46,6 +46,8 @@
<system:String x:Key="flowlauncher_plugin_program_pls_select_program_source">Por favor, seleccione la ruta del programa</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source">¿Está seguro de que desea eliminar las fuentes del programa seleccionadas?</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_not_user_added">Please select program sources that are not added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_user_added">Please select program sources that are added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_duplicate_program_source">Ya existe otra fuente de programa con la misma ubicación.</system:String>
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">Fuente de Programa</system:String>

View file

@ -46,6 +46,8 @@
<system:String x:Key="flowlauncher_plugin_program_pls_select_program_source">Veuillez sélectionner une source de programme</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source">Êtes-vous sûr de vouloir supprimer les sources de programmes sélectionnées ?</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_not_user_added">Veuillez sélectionner les sources de programmes qui n'ont pas été ajoutées par vous-même</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_user_added">Veuillez sélectionner les sources de programmes qui ont été ajoutées par vous-même</system:String>
<system:String x:Key="flowlauncher_plugin_program_duplicate_program_source">Il existe déjà une autre source de programme ayant le même emplacement.</system:String>
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">Source du programme</system:String>

View file

@ -46,6 +46,8 @@
<system:String x:Key="flowlauncher_plugin_program_pls_select_program_source">אנא בחר מקור תוכנה</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source">האם אתה בטוח שברצונך למחוק את מקורות התוכניות שנבחרו?</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_not_user_added">Please select program sources that are not added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_user_added">Please select program sources that are added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_duplicate_program_source">מקור תוכנה נוסף עם אותו מיקום כבר קיים.</system:String>
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">מקור תוכנה</system:String>

View file

@ -46,6 +46,8 @@
<system:String x:Key="flowlauncher_plugin_program_pls_select_program_source">Seleziona la sorgente del programma</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source">Sei sicuro di voler cancellare le sorgenti dei programmi selezionate?</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_not_user_added">Please select program sources that are not added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_user_added">Please select program sources that are added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_duplicate_program_source">Another program source with the same location already exists.</system:String>
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">Program Source</system:String>

View file

@ -46,6 +46,8 @@
<system:String x:Key="flowlauncher_plugin_program_pls_select_program_source">Please select a program source</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source">Are your sure to delete {0}?</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_not_user_added">Please select program sources that are not added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_user_added">Please select program sources that are added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_duplicate_program_source">Another program source with the same location already exists.</system:String>
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">Program Source</system:String>

View file

@ -46,6 +46,8 @@
<system:String x:Key="flowlauncher_plugin_program_pls_select_program_source">프로그램 검색 출처를 선택하세요</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source">선택하신 프로그램 출처를 삭제하시겠습니까?</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_not_user_added">Please select program sources that are not added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_user_added">Please select program sources that are added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_duplicate_program_source">Another program source with the same location already exists.</system:String>
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">Program Source</system:String>

View file

@ -46,6 +46,8 @@
<system:String x:Key="flowlauncher_plugin_program_pls_select_program_source">Vennligst velg en programkilde</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source">Er du sikker på at du vil slette de valgte programkildene?</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_not_user_added">Please select program sources that are not added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_user_added">Please select program sources that are added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_duplicate_program_source">Det finnes allerede en annen programkilde med samme plassering.</system:String>
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">Programkilde</system:String>

View file

@ -46,6 +46,8 @@
<system:String x:Key="flowlauncher_plugin_program_pls_select_program_source">Please select a program source</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source">Are you sure you want to delete the selected program sources?</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_not_user_added">Please select program sources that are not added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_user_added">Please select program sources that are added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_duplicate_program_source">Another program source with the same location already exists.</system:String>
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">Program Source</system:String>

View file

@ -46,6 +46,8 @@
<system:String x:Key="flowlauncher_plugin_program_pls_select_program_source">Musisz wybrać katalog programu</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source">Czy na pewno chcesz usunąć wybrane źródła programów?</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_not_user_added">Please select program sources that are not added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_user_added">Please select program sources that are added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_duplicate_program_source">Inne źródło programu z tą samą lokalizacją już istnieje.</system:String>
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">Źródło programu</system:String>

View file

@ -46,6 +46,8 @@
<system:String x:Key="flowlauncher_plugin_program_pls_select_program_source">Please select a program source</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source">Are you sure you want to delete the selected program sources?</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_not_user_added">Please select program sources that are not added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_user_added">Please select program sources that are added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_duplicate_program_source">Another program source with the same location already exists.</system:String>
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">Program Source</system:String>

View file

@ -46,6 +46,8 @@
<system:String x:Key="flowlauncher_plugin_program_pls_select_program_source">Por favor selecione uma origem de programas</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source">Tem a certeza de que deseja remover as origens selecionadas?</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_not_user_added">Selecione as origens de programas NÃO adicionadas por si</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_user_added">Selecione as origens de programas adicionadas por si</system:String>
<system:String x:Key="flowlauncher_plugin_program_duplicate_program_source">Já existe uma origem de programas com esta localização.</system:String>
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">Origem de programas</system:String>

View file

@ -46,6 +46,8 @@
<system:String x:Key="flowlauncher_plugin_program_pls_select_program_source">Пожалуйста, выберите источник программы</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source">Вы уверены, что хотите удалить выбранные источники программ?</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_not_user_added">Please select program sources that are not added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_user_added">Please select program sources that are added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_duplicate_program_source">Другой источник программы с таким же расположением уже существует.</system:String>
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">Источник программы</system:String>

View file

@ -46,6 +46,8 @@
<system:String x:Key="flowlauncher_plugin_program_pls_select_program_source">Prosím, zadajte zdroj programu</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source">Naozaj chcete odstrániť vybrané zdroje programov?</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_not_user_added">Vyberte zdroje programov, ktoré ste nepridali vy</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_user_added">Vyberte zdroje programov, ktoré ste pridali vy</system:String>
<system:String x:Key="flowlauncher_plugin_program_duplicate_program_source">Iný zdroj programu s rovnakým umiestnením už existuje.</system:String>
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">Zdroj programu</system:String>

View file

@ -46,6 +46,8 @@
<system:String x:Key="flowlauncher_plugin_program_pls_select_program_source">Please select a program source</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source">Are you sure you want to delete the selected program sources?</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_not_user_added">Please select program sources that are not added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_user_added">Please select program sources that are added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_duplicate_program_source">Another program source with the same location already exists.</system:String>
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">Program Source</system:String>

View file

@ -46,6 +46,8 @@
<system:String x:Key="flowlauncher_plugin_program_pls_select_program_source">İşlem yapmak istediğiniz klasörü seçin.</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source">Are you sure you want to delete the selected program sources?</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_not_user_added">Please select program sources that are not added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_user_added">Please select program sources that are added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_duplicate_program_source">Another program source with the same location already exists.</system:String>
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">Program Source</system:String>

View file

@ -46,6 +46,8 @@
<system:String x:Key="flowlauncher_plugin_program_pls_select_program_source">Будь ласка, виберіть джерело програми</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source">Ви впевнені, що хочете видалити вибрані джерела програм?</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_not_user_added">Please select program sources that are not added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_user_added">Please select program sources that are added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_duplicate_program_source">Інше програмне джерело з тим самим розташуванням вже існує.</system:String>
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">Вихідний код програми</system:String>

View file

@ -46,6 +46,8 @@
<system:String x:Key="flowlauncher_plugin_program_pls_select_program_source">Hãy chọn một nguồn dữ liệu</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source">Bạn có chắc chắn là muốn xóa các đặt hàng đã chọn?</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_not_user_added">Please select program sources that are not added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_user_added">Please select program sources that are added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_duplicate_program_source">Đã tồn tại một nguồn chương trình khác có cùng vị trí.</system:String>
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">Nguồn chương trình</system:String>

View file

@ -46,6 +46,8 @@
<system:String x:Key="flowlauncher_plugin_program_pls_select_program_source">请先选择一项</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source">您确定要删除选定的程序源吗?</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_not_user_added">Please select program sources that are not added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_user_added">Please select program sources that are added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_duplicate_program_source">相同位置存在另一个程序源。</system:String>
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">程序源</system:String>

View file

@ -46,6 +46,8 @@
<system:String x:Key="flowlauncher_plugin_program_pls_select_program_source">請先選擇一項</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source">Are you sure you want to delete the selected program sources?</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_not_user_added">Please select program sources that are not added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_user_added">Please select program sources that are added by you</system:String>
<system:String x:Key="flowlauncher_plugin_program_duplicate_program_source">Another program source with the same location already exists.</system:String>
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">Program Source</system:String>

View file

@ -203,6 +203,12 @@
Margin="{StaticResource SettingPanelItemLeftTopBottomMargin}"
Click="btnEditProgramSource_OnClick"
Content="{DynamicResource flowlauncher_plugin_program_edit}" />
<Button
x:Name="btnDeleteProgramSource"
MinWidth="100"
Margin="{StaticResource SettingPanelItemLeftTopBottomMargin}"
Click="btnDeleteProgramSource_OnClick"
Content="{DynamicResource flowlauncher_plugin_program_delete}" />
<Button
x:Name="btnAddProgramSource"
MinWidth="100"

View file

@ -133,6 +133,7 @@ namespace Flow.Launcher.Plugin.Program.Views
{
btnProgramSourceStatus.Visibility = Visibility.Hidden;
btnEditProgramSource.Visibility = Visibility.Hidden;
btnDeleteProgramSource.Visibility = Visibility.Hidden;
}
if (programSourceView.Items.Count > 0
@ -141,6 +142,7 @@ namespace Flow.Launcher.Plugin.Program.Views
{
btnProgramSourceStatus.Visibility = Visibility.Visible;
btnEditProgramSource.Visibility = Visibility.Visible;
btnDeleteProgramSource.Visibility = Visibility.Visible;
}
programSourceView.Items.Refresh();
@ -270,8 +272,8 @@ namespace Flow.Launcher.Plugin.Program.Views
if (directoriesToAdd.Count > 0)
{
directoriesToAdd.ForEach(x => _settings.ProgramSources.Add(x));
directoriesToAdd.ForEach(x => ProgramSettingDisplayList.Add(x));
directoriesToAdd.ForEach(_settings.ProgramSources.Add);
directoriesToAdd.ForEach(ProgramSettingDisplayList.Add);
ViewRefresh();
ReIndexing();
@ -296,24 +298,11 @@ namespace Flow.Launcher.Plugin.Program.Views
if (selectedItems.Count == 0)
{
string msg = context.API.GetTranslation("flowlauncher_plugin_program_pls_select_program_source");
context.API.ShowMsgBox(msg);
context.API.ShowMsgBox(context.API.GetTranslation("flowlauncher_plugin_program_pls_select_program_source"));
return;
}
if (IsAllItemsUserAdded(selectedItems))
{
var msg = string.Format(
context.API.GetTranslation("flowlauncher_plugin_program_delete_program_source"));
if (context.API.ShowMsgBox(msg, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
{
return;
}
DeleteProgramSources(selectedItems);
}
else if (HasMoreOrEqualEnabledItems(selectedItems))
if (HasMoreOrEqualEnabledItems(selectedItems))
{
await ProgramSettingDisplay.SetProgramSourcesStatusAsync(selectedItems, false);
@ -341,10 +330,9 @@ namespace Flow.Launcher.Plugin.Program.Views
private void GridViewColumnHeaderClickedHandler(object sender, RoutedEventArgs e)
{
var headerClicked = e.OriginalSource as GridViewColumnHeader;
ListSortDirection direction;
if (headerClicked != null)
if (e.OriginalSource is GridViewColumnHeader headerClicked)
{
if (headerClicked.Role != GridViewColumnHeaderRole.Padding)
{
@ -380,7 +368,7 @@ namespace Flow.Launcher.Plugin.Program.Views
var dataView = CollectionViewSource.GetDefaultView(programSourceView.ItemsSource);
dataView.SortDescriptions.Clear();
SortDescription sd = new SortDescription(sortBy, direction);
var sd = new SortDescription(sortBy, direction);
dataView.SortDescriptions.Add(sd);
dataView.Refresh();
}
@ -397,11 +385,7 @@ namespace Flow.Launcher.Plugin.Program.Views
.SelectedItems.Cast<ProgramSource>()
.ToList();
if (IsAllItemsUserAdded(selectedItems))
{
btnProgramSourceStatus.Content = context.API.GetTranslation("flowlauncher_plugin_program_delete");
}
else if (HasMoreOrEqualEnabledItems(selectedItems))
if (HasMoreOrEqualEnabledItems(selectedItems))
{
btnProgramSourceStatus.Content = context.API.GetTranslation("flowlauncher_plugin_program_disable");
}
@ -420,6 +404,41 @@ namespace Flow.Launcher.Plugin.Program.Views
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "<Pending>")]
private async void btnDeleteProgramSource_OnClick(object sender, RoutedEventArgs e)
{
var selectedItems = programSourceView
.SelectedItems.Cast<ProgramSource>()
.ToList();
if (selectedItems.Count == 0)
{
context.API.ShowMsgBox(context.API.GetTranslation("flowlauncher_plugin_program_pls_select_program_source"));
return;
}
if (!IsAllItemsUserAdded(selectedItems))
{
context.API.ShowMsgBox(context.API.GetTranslation("flowlauncher_plugin_program_delete_program_source_select_user_added"));
return;
}
if (context.API.ShowMsgBox(context.API.GetTranslation("flowlauncher_plugin_program_delete_program_source"),
string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
{
return;
}
DeleteProgramSources(selectedItems);
if (await selectedItems.IsReindexRequiredAsync())
ReIndexing();
programSourceView.SelectedItems.Clear();
ViewRefresh();
}
private bool IsAllItemsUserAdded(List<ProgramSource> items)
{
return items.All(x => _settings.ProgramSources.Any(y => y.UniqueIdentifier == x.UniqueIdentifier));
@ -427,11 +446,14 @@ namespace Flow.Launcher.Plugin.Program.Views
private void ListView_SizeChanged(object sender, SizeChangedEventArgs e)
{
ListView listView = sender as ListView;
GridView gView = listView.View as GridView;
var listView = sender as ListView;
var gView = listView.View as GridView;
var workingWidth =
listView.ActualWidth - SystemParameters.VerticalScrollBarWidth; // take into account vertical scrollbar
if (workingWidth <= 0) return;
var col1 = 0.25;
var col2 = 0.15;
var col3 = 0.60;

View file

@ -21,16 +21,15 @@ namespace Flow.Launcher.Plugin.Sys
ListView listView = sender as ListView;
GridView gView = listView.View as GridView;
var workingWidth = listView.ActualWidth - SystemParameters.VerticalScrollBarWidth; // take into account vertical scrollbar
var workingWidth =
listView.ActualWidth - SystemParameters.VerticalScrollBarWidth; // take into account vertical scrollbar
if (workingWidth <= 0) return;
var col1 = 0.2;
var col2 = 0.6;
var col3 = 0.2;
if (workingWidth <= 0)
{
return;
}
gView.Columns[0].Width = workingWidth * col1;
gView.Columns[1].Width = workingWidth * col2;
gView.Columns[2].Width = workingWidth * col3;