mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge branch 'dev' of github.com:Flow-Launcher/Flow.Launcher into ProgressBarDispatcher
This commit is contained in:
commit
71938cdbf8
55 changed files with 3129 additions and 28731 deletions
|
|
@ -1,4 +1,4 @@
|
|||
using System.Diagnostics;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
|
|
@ -21,6 +21,7 @@ namespace Flow.Launcher.Infrastructure
|
|||
public static readonly string PreinstalledDirectory = Path.Combine(ProgramDirectory, Plugins);
|
||||
public const string Issue = "https://github.com/Flow-Launcher/Flow.Launcher/issues/new";
|
||||
public static readonly string Version = FileVersionInfo.GetVersionInfo(Assembly.Location.NonNull()).ProductVersion;
|
||||
public static readonly string Dev = "Dev";
|
||||
public const string Documentation = "https://flowlauncher.com/docs/#/usage-tips";
|
||||
|
||||
public static readonly int ThumbnailSize = 64;
|
||||
|
|
|
|||
|
|
@ -58,22 +58,11 @@
|
|||
<PackageReference Include="NLog.Schema" Version="4.7.10" />
|
||||
<PackageReference Include="NLog.Web.AspNetCore" Version="4.13.0" />
|
||||
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="5.0.2" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="5.0.3" />
|
||||
<!--ToolGood.Words.Pinyin v3.0.2.6 results in high memory usage when search with pinyin is enabled-->
|
||||
<!--Bumping to it or higher needs to test and ensure this is no longer a problem-->
|
||||
<PackageReference Include="ToolGood.Words.Pinyin" Version="3.0.1.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="pinyindb\pinyin_gwoyeu_mapping.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="pinyindb\pinyin_mapping.xml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="pinyindb\unicode_to_hanyu_pinyin.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Diagnostics;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using NLog;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Flow.Launcher.Infrastructure
|
|||
|
||||
private List<int> originalIndexs = new List<int>();
|
||||
private List<int> translatedIndexs = new List<int>();
|
||||
private int translaedLength = 0;
|
||||
private int translatedLength = 0;
|
||||
|
||||
public string key { get; private set; }
|
||||
|
||||
|
|
@ -32,13 +32,13 @@ namespace Flow.Launcher.Infrastructure
|
|||
originalIndexs.Add(originalIndex);
|
||||
translatedIndexs.Add(translatedIndex);
|
||||
translatedIndexs.Add(translatedIndex + length);
|
||||
translaedLength += length - 1;
|
||||
translatedLength += length - 1;
|
||||
}
|
||||
|
||||
public int MapToOriginalIndex(int translatedIndex)
|
||||
{
|
||||
if (translatedIndex > translatedIndexs.Last())
|
||||
return translatedIndex - translaedLength - 1;
|
||||
return translatedIndex - translatedLength - 1;
|
||||
|
||||
int lowerBound = 0;
|
||||
int upperBound = originalIndexs.Count - 1;
|
||||
|
|
@ -83,7 +83,7 @@ namespace Flow.Launcher.Infrastructure
|
|||
translatedIndex < translatedIndexs[upperBound * 2])
|
||||
{
|
||||
int indexDef = 0;
|
||||
|
||||
|
||||
for (int j = 0; j < upperBound; j++)
|
||||
{
|
||||
indexDef += translatedIndexs[j * 2 + 1] - translatedIndexs[j * 2];
|
||||
|
|
@ -102,9 +102,24 @@ namespace Flow.Launcher.Infrastructure
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Translate a language to English letters using a given rule.
|
||||
/// </summary>
|
||||
public interface IAlphabet
|
||||
{
|
||||
/// <summary>
|
||||
/// Translate a string to English letters, using a given rule.
|
||||
/// </summary>
|
||||
/// <param name="stringToTranslate">String to translate.</param>
|
||||
/// <returns></returns>
|
||||
public (string translation, TranslationMapping map) Translate(string stringToTranslate);
|
||||
|
||||
/// <summary>
|
||||
/// Determine if a string can be translated to English letter with this Alphabet.
|
||||
/// </summary>
|
||||
/// <param name="stringToTranslate">String to translate.</param>
|
||||
/// <returns></returns>
|
||||
public bool CanBeTranslated(string stringToTranslate);
|
||||
}
|
||||
|
||||
public class PinyinAlphabet : IAlphabet
|
||||
|
|
@ -119,63 +134,70 @@ namespace Flow.Launcher.Infrastructure
|
|||
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
|
||||
}
|
||||
|
||||
public bool CanBeTranslated(string stringToTranslate)
|
||||
{
|
||||
return WordsHelper.HasChinese(stringToTranslate);
|
||||
}
|
||||
|
||||
public (string translation, TranslationMapping map) Translate(string content)
|
||||
{
|
||||
if (_settings.ShouldUsePinyin)
|
||||
{
|
||||
if (!_pinyinCache.ContainsKey(content))
|
||||
{
|
||||
if (WordsHelper.HasChinese(content))
|
||||
{
|
||||
var resultList = WordsHelper.GetPinyinList(content);
|
||||
|
||||
StringBuilder resultBuilder = new StringBuilder();
|
||||
TranslationMapping map = new TranslationMapping();
|
||||
|
||||
bool pre = false;
|
||||
|
||||
for (int i = 0; i < resultList.Length; i++)
|
||||
{
|
||||
if (content[i] >= 0x3400 && content[i] <= 0x9FD5)
|
||||
{
|
||||
map.AddNewIndex(i, resultBuilder.Length, resultList[i].Length + 1);
|
||||
resultBuilder.Append(' ');
|
||||
resultBuilder.Append(resultList[i]);
|
||||
pre = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pre)
|
||||
{
|
||||
pre = false;
|
||||
resultBuilder.Append(' ');
|
||||
}
|
||||
|
||||
resultBuilder.Append(resultList[i]);
|
||||
}
|
||||
}
|
||||
|
||||
map.endConstruct();
|
||||
|
||||
var key = resultBuilder.ToString();
|
||||
map.setKey(key);
|
||||
|
||||
return _pinyinCache[content] = (key, map);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (content, null);
|
||||
}
|
||||
return BuildCacheFromContent(content);
|
||||
}
|
||||
else
|
||||
{
|
||||
return _pinyinCache[content];
|
||||
}
|
||||
}
|
||||
return (content, null);
|
||||
}
|
||||
|
||||
private (string translation, TranslationMapping map) BuildCacheFromContent(string content)
|
||||
{
|
||||
if (WordsHelper.HasChinese(content))
|
||||
{
|
||||
var resultList = WordsHelper.GetPinyinList(content);
|
||||
|
||||
StringBuilder resultBuilder = new StringBuilder();
|
||||
TranslationMapping map = new TranslationMapping();
|
||||
|
||||
bool pre = false;
|
||||
|
||||
for (int i = 0; i < resultList.Length; i++)
|
||||
{
|
||||
if (content[i] >= 0x3400 && content[i] <= 0x9FD5)
|
||||
{
|
||||
map.AddNewIndex(i, resultBuilder.Length, resultList[i].Length + 1);
|
||||
resultBuilder.Append(' ');
|
||||
resultBuilder.Append(resultList[i]);
|
||||
pre = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pre)
|
||||
{
|
||||
pre = false;
|
||||
resultBuilder.Append(' ');
|
||||
}
|
||||
|
||||
resultBuilder.Append(resultList[i]);
|
||||
}
|
||||
}
|
||||
|
||||
map.endConstruct();
|
||||
|
||||
var key = resultBuilder.ToString();
|
||||
map.setKey(key);
|
||||
|
||||
return _pinyinCache[content] = (key, map);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (content, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Flow.Launcher.Plugin.SharedModels;
|
||||
using Flow.Launcher.Plugin.SharedModels;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
|
@ -60,8 +60,13 @@ namespace Flow.Launcher.Infrastructure
|
|||
return new MatchResult(false, UserSettingSearchPrecision);
|
||||
|
||||
query = query.Trim();
|
||||
TranslationMapping translationMapping;
|
||||
(stringToCompare, translationMapping) = _alphabet?.Translate(stringToCompare) ?? (stringToCompare, null);
|
||||
TranslationMapping translationMapping = null;
|
||||
if (_alphabet is not null && !_alphabet.CanBeTranslated(query))
|
||||
{
|
||||
// We assume that if a query can be translated (containing characters of a language, like Chinese)
|
||||
// it actually means user doesn't want it to be translated to English letters.
|
||||
(stringToCompare, translationMapping) = _alphabet.Translate(stringToCompare);
|
||||
}
|
||||
|
||||
var currentAcronymQueryIndex = 0;
|
||||
var acronymMatchData = new List<int>();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Flow.Launcher.Infrastructure.UserSettings
|
||||
{
|
||||
public abstract class ShortcutBaseModel
|
||||
{
|
||||
public string Key { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public Func<string> Expand { get; set; } = () => { return ""; };
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is ShortcutBaseModel other &&
|
||||
Key == other.Key;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Key.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public class CustomShortcutModel : ShortcutBaseModel
|
||||
{
|
||||
public string Value { get; set; }
|
||||
|
||||
[JsonConstructorAttribute]
|
||||
public CustomShortcutModel(string key, string value)
|
||||
{
|
||||
Key = key;
|
||||
Value = value;
|
||||
Expand = () => { return Value; };
|
||||
}
|
||||
|
||||
public void Deconstruct(out string key, out string value)
|
||||
{
|
||||
key = Key;
|
||||
value = Value;
|
||||
}
|
||||
|
||||
public static implicit operator (string Key, string Value)(CustomShortcutModel shortcut)
|
||||
{
|
||||
return (shortcut.Key, shortcut.Value);
|
||||
}
|
||||
|
||||
public static implicit operator CustomShortcutModel((string Key, string Value) shortcut)
|
||||
{
|
||||
return new CustomShortcutModel(shortcut.Key, shortcut.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public class BuiltinShortcutModel : ShortcutBaseModel
|
||||
{
|
||||
public string Description { get; set; }
|
||||
|
||||
public BuiltinShortcutModel(string key, string description, Func<string> expand)
|
||||
{
|
||||
Key = key;
|
||||
Description = description;
|
||||
Expand = expand ?? (() => { return ""; });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Collections.ObjectModel;
|
||||
using System.Drawing;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Windows;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedModels;
|
||||
using Flow.Launcher;
|
||||
|
|
@ -51,6 +52,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
public double SettingWindowHeight { get; set; } = 700;
|
||||
public double SettingWindowTop { get; set; }
|
||||
public double SettingWindowLeft { get; set; }
|
||||
public System.Windows.WindowState SettingWindowState { get; set; } = WindowState.Normal;
|
||||
|
||||
public int CustomExplorerIndex { get; set; } = 0;
|
||||
|
||||
|
|
@ -129,8 +131,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
PrivateArg = "-private",
|
||||
EnablePrivate = false,
|
||||
Editable = false
|
||||
}
|
||||
,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Name = "MS Edge",
|
||||
|
|
@ -186,6 +187,13 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
|
||||
public ObservableCollection<CustomPluginHotkey> CustomPluginHotkeys { get; set; } = new ObservableCollection<CustomPluginHotkey>();
|
||||
|
||||
public ObservableCollection<CustomShortcutModel> CustomShortcuts { get; set; } = new ObservableCollection<CustomShortcutModel>();
|
||||
|
||||
[JsonIgnore]
|
||||
public ObservableCollection<BuiltinShortcutModel> BuiltinShortcuts { get; set; } = new ObservableCollection<BuiltinShortcutModel>() {
|
||||
new BuiltinShortcutModel("{clipboard}", "shortcut_clipboard_description", Clipboard.GetText)
|
||||
};
|
||||
|
||||
public bool DontPromptUpdateMsg { get; set; }
|
||||
public bool EnableUpdateLog { get; set; }
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -37,7 +37,11 @@ namespace Flow.Launcher.Plugin
|
|||
/// user's clipboard when Ctrl + C is pressed on a result. If the text is a file/directory path
|
||||
/// flow will copy the actual file/folder instead of just the path text.
|
||||
/// </summary>
|
||||
public string CopyText { get; set; } = string.Empty;
|
||||
public string CopyText
|
||||
{
|
||||
get => string.IsNullOrEmpty(_copyText) ? SubTitle : _copyText;
|
||||
set => _copyText = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This holds the text which can be provided by plugin to help Flow autocomplete text
|
||||
|
|
@ -81,6 +85,7 @@ namespace Flow.Launcher.Plugin
|
|||
/// Delegate to Get Image Source
|
||||
/// </summary>
|
||||
public IconDelegate Icon;
|
||||
private string _copyText = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Information for Glyph Icon (Prioritized than IcoPath/Icon if user enable Glyph Icons)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ namespace Flow.Launcher.Test.Plugins
|
|||
var app = new UWP.Application();
|
||||
|
||||
// Act
|
||||
var result = app.FormattedPriReferenceValue(packageName, rawPriReferenceValue);
|
||||
var result = UWP.Application.FormattedPriReferenceValue(packageName, rawPriReferenceValue);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result == expectedFormat,
|
||||
|
|
|
|||
20
Flow.Launcher/Converters/TranslationConverter.cs
Normal file
20
Flow.Launcher/Converters/TranslationConverter.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
using Flow.Launcher.Core.Resource;
|
||||
|
||||
namespace Flow.Launcher.Converters
|
||||
{
|
||||
public class TranlationConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
var key = value.ToString();
|
||||
if (String.IsNullOrEmpty(key))
|
||||
return key;
|
||||
return InternationalizationManager.Instance.GetTranslation(key);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, System.Type targetType, object parameter, CultureInfo culture) => throw new System.InvalidOperationException();
|
||||
}
|
||||
}
|
||||
160
Flow.Launcher/CustomShortcutSetting.xaml
Normal file
160
Flow.Launcher/CustomShortcutSetting.xaml
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
<Window
|
||||
x:Class="Flow.Launcher.CustomShortcutSetting"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:flowlauncher="clr-namespace:Flow.Launcher"
|
||||
Title="{DynamicResource customeQueryShortcutTitle}"
|
||||
Width="530"
|
||||
Background="{DynamicResource PopuBGColor}"
|
||||
Foreground="{DynamicResource PopupTextColor}"
|
||||
Icon="Images\app.png"
|
||||
ResizeMode="NoResize"
|
||||
SizeToContent="Height"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
DataContext="{Binding RelativeSource={RelativeSource Self}}">
|
||||
<WindowChrome.WindowChrome>
|
||||
<WindowChrome CaptionHeight="32" ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
|
||||
</WindowChrome.WindowChrome>
|
||||
<Window.InputBindings>
|
||||
<KeyBinding Key="Escape" Command="Close" />
|
||||
</Window.InputBindings>
|
||||
<Window.CommandBindings>
|
||||
<CommandBinding Command="Close" Executed="cmdEsc_OnPress" />
|
||||
</Window.CommandBindings>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="80" />
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Grid.Row="0">
|
||||
<StackPanel>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button
|
||||
Grid.Column="4"
|
||||
Click="BtnCancel_OnClick"
|
||||
Style="{StaticResource TitleBarCloseButtonStyle}">
|
||||
<Path
|
||||
Width="46"
|
||||
Height="32"
|
||||
Data="M 18,11 27,20 M 18,20 27,11"
|
||||
Stroke="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
|
||||
StrokeThickness="1">
|
||||
<Path.Style>
|
||||
<Style TargetType="Path">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Path=IsActive, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Value="False">
|
||||
<Setter Property="Opacity" Value="0.5" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Path.Style>
|
||||
</Path>
|
||||
</Button>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="26,0,26,0">
|
||||
<StackPanel Grid.Row="0" Margin="0,0,0,12">
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Margin="0,0,0,0"
|
||||
FontFamily="Segoe UI"
|
||||
FontSize="20"
|
||||
FontWeight="SemiBold"
|
||||
Text="{DynamicResource customQueryShortcut}"
|
||||
TextAlignment="Left" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
FontSize="14"
|
||||
Text="{DynamicResource customeQueryShortcutTips}"
|
||||
TextAlignment="Left"
|
||||
TextWrapping="WrapWithOverflow" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Margin="0,20,0,0" Orientation="Horizontal">
|
||||
<Grid Width="470">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Text="{DynamicResource customShortcut}" />
|
||||
<TextBox
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Margin="10"
|
||||
Text="{Binding Key}"
|
||||
/>
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Text="{DynamicResource customShortcutExpansion}" />
|
||||
|
||||
<DockPanel
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
LastChildFill="True">
|
||||
<Button
|
||||
x:Name="btnTestShortcut"
|
||||
Margin="0,0,10,0"
|
||||
Padding="10,5,10,5"
|
||||
Click="BtnTestShortcut_OnClick"
|
||||
Content="{DynamicResource preview}"
|
||||
DockPanel.Dock="Right" />
|
||||
<TextBox
|
||||
x:Name="tbExpand"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding Value}"
|
||||
VerticalAlignment="Center" />
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Margin="0,14,0,0"
|
||||
Background="{DynamicResource PopupButtonAreaBGColor}"
|
||||
BorderBrush="{DynamicResource PopupButtonAreaBorderColor}"
|
||||
BorderThickness="0,1,0,0">
|
||||
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
|
||||
<Button
|
||||
x:Name="btnCancel"
|
||||
MinWidth="140"
|
||||
Margin="10,0,5,0"
|
||||
Click="BtnCancel_OnClick"
|
||||
Content="{DynamicResource cancel}" />
|
||||
<Button
|
||||
x:Name="btnAdd"
|
||||
MinWidth="140"
|
||||
Margin="5,0,10,0"
|
||||
Click="BtnAdd_OnClick"
|
||||
Style="{StaticResource AccentButtonStyle}">
|
||||
<TextBlock x:Name="lblAdd" Text="{DynamicResource done}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
73
Flow.Launcher/CustomShortcutSetting.xaml.cs
Normal file
73
Flow.Launcher/CustomShortcutSetting.xaml.cs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
using Flow.Launcher.Core.Resource;
|
||||
using Flow.Launcher.ViewModel;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Flow.Launcher
|
||||
{
|
||||
public partial class CustomShortcutSetting : Window
|
||||
{
|
||||
private SettingWindowViewModel viewModel;
|
||||
public string Key { get; set; } = String.Empty;
|
||||
public string Value { get; set; } = String.Empty;
|
||||
private string originalKey { get; init; } = null;
|
||||
private string originalValue { get; init; } = null;
|
||||
private bool update { get; init; } = false;
|
||||
|
||||
public CustomShortcutSetting(SettingWindowViewModel vm)
|
||||
{
|
||||
viewModel = vm;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public CustomShortcutSetting(string key, string value, SettingWindowViewModel vm)
|
||||
{
|
||||
viewModel = vm;
|
||||
Key = key;
|
||||
Value = value;
|
||||
originalKey = key;
|
||||
originalValue = value;
|
||||
update = true;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void BtnCancel_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void BtnAdd_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (String.IsNullOrEmpty(Key) || String.IsNullOrEmpty(Value))
|
||||
{
|
||||
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("emptyShortcut"));
|
||||
return;
|
||||
}
|
||||
// Check if key is modified or adding a new one
|
||||
if (((update && originalKey != Key) || !update)
|
||||
&& viewModel.ShortcutExists(Key))
|
||||
{
|
||||
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("duplicateShortcut"));
|
||||
return;
|
||||
}
|
||||
DialogResult = !update || originalKey != Key || originalValue != Value;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void cmdEsc_OnPress(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void BtnTestShortcut_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
App.API.ChangeQuery(tbExpand.Text);
|
||||
Application.Current.MainWindow.Show();
|
||||
Application.Current.MainWindow.Opacity = 1;
|
||||
Application.Current.MainWindow.Focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -89,7 +89,7 @@
|
|||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="InputSimulator" Version="1.0.4" />
|
||||
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.2" />
|
||||
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
|
||||
<PackageReference Include="ModernWpfUI" Version="0.9.4" />
|
||||
<PackageReference Include="NHotkey.Wpf" Version="2.1.0" />
|
||||
<PackageReference Include="NuGet.CommandLine" Version="5.7.2">
|
||||
|
|
@ -98,6 +98,7 @@
|
|||
</PackageReference>
|
||||
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" />
|
||||
<PackageReference Include="SharpVectors" Version="1.7.6" />
|
||||
<PackageReference Include="VirtualizingWrapPanel" Version="1.5.7" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -115,12 +116,12 @@
|
|||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||
<Exec Command="taskkill /f /fi "IMAGENAME eq Flow.Launcher.exe"" />
|
||||
</Target>
|
||||
|
||||
|
||||
<Target Name="RemoveDuplicateAnalyzers" BeforeTargets="CoreCompile">
|
||||
<!-- Work around https://github.com/dotnet/wpf/issues/6792 -->
|
||||
|
||||
<ItemGroup>
|
||||
<FilteredAnalyzer Include="@(Analyzer->Distinct())" />
|
||||
<FilteredAnalyzer Include="@(Analyzer->Distinct())" />
|
||||
<Analyzer Remove="@(Analyzer)" />
|
||||
<Analyzer Include="@(FilteredAnalyzer)" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -38,9 +38,8 @@
|
|||
FontSize="13"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource Color05B}"
|
||||
Visibility="Visible">
|
||||
Press key
|
||||
</TextBlock>
|
||||
Text="{DynamicResource flowlauncherPressHotkey}"
|
||||
Visibility="Visible" />
|
||||
</Border>
|
||||
</Popup>
|
||||
|
||||
|
|
@ -49,8 +48,8 @@
|
|||
Margin="0,0,18,0"
|
||||
VerticalContentAlignment="Center"
|
||||
input:InputMethod.IsInputMethodEnabled="False"
|
||||
LostFocus="tbHotkey_LostFocus"
|
||||
PreviewKeyDown="TbHotkey_OnPreviewKeyDown"
|
||||
TabIndex="100"
|
||||
LostFocus="tbHotkey_LostFocus"/>
|
||||
TabIndex="100" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
|
@ -9,6 +9,7 @@ using Flow.Launcher.Helper;
|
|||
using Flow.Launcher.Infrastructure.Hotkey;
|
||||
using Flow.Launcher.Plugin;
|
||||
using System.Threading;
|
||||
using System.Windows.Interop;
|
||||
|
||||
namespace Flow.Launcher
|
||||
{
|
||||
|
|
@ -113,7 +114,7 @@ namespace Flow.Launcher
|
|||
private void tbHotkey_LostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
tbMsg.Text = tbMsgTextOriginal;
|
||||
tbMsg.Foreground = tbMsgForegroundColorOriginal;
|
||||
tbMsg.SetResourceReference(TextBox.ForegroundProperty, "Color05B");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
<system:String x:Key="PositionResetToolTip">Reset search window position</system:String>
|
||||
|
||||
<!-- Setting General -->
|
||||
<system:String x:Key="flowlauncher_settings">Flow Launcher Settings</system:String>
|
||||
<system:String x:Key="flowlauncher_settings">Settings</system:String>
|
||||
<system:String x:Key="general">General</system:String>
|
||||
<system:String x:Key="portableMode">Portable Mode</system:String>
|
||||
<system:String x:Key="portableModeToolTIp">Store all settings and user data in one folder (Useful when used with removable drives or cloud services).</system:String>
|
||||
|
|
@ -92,6 +92,7 @@
|
|||
<system:String x:Key="plugin_query_time">Query time:</system:String>
|
||||
<system:String x:Key="plugin_query_version">| Version</system:String>
|
||||
<system:String x:Key="plugin_query_web">Website</system:String>
|
||||
<system:String x:Key="plugin_uninstall">Uninstall</system:String>
|
||||
|
||||
|
||||
<!-- Setting Plugin Store -->
|
||||
|
|
@ -104,9 +105,9 @@
|
|||
<system:String x:Key="installbtn">Install</system:String>
|
||||
<system:String x:Key="uninstallbtn">Uninstall</system:String>
|
||||
<system:String x:Key="updatebtn">Update</system:String>
|
||||
<system:String x:Key="LabelInstalledToolTip">Plug-in already installed</system:String>
|
||||
<system:String x:Key="LabelInstalledToolTip">Plugin already installed</system:String>
|
||||
<system:String x:Key="LabelNew">New Version</system:String>
|
||||
<system:String x:Key="LabelNewToolTip">This plug-in has been updated within the last 7 days</system:String>
|
||||
<system:String x:Key="LabelNewToolTip">This plugin has been updated within the last 7 days</system:String>
|
||||
<system:String x:Key="LabelUpdateToolTip">New Update is Available</system:String>
|
||||
|
||||
|
||||
|
|
@ -144,18 +145,26 @@
|
|||
<system:String x:Key="showOpenResultHotkey">Show Hotkey</system:String>
|
||||
<system:String x:Key="showOpenResultHotkeyToolTip">Show result selection hotkey with results.</system:String>
|
||||
<system:String x:Key="customQueryHotkey">Custom Query Hotkey</system:String>
|
||||
<system:String x:Key="customQueryShortcut">Custom Query Shortcut</system:String>
|
||||
<system:String x:Key="builtinShortcuts">Built-in Shortcuts</system:String>
|
||||
<system:String x:Key="customQuery">Query</system:String>
|
||||
<system:String x:Key="customShortcut">Shortcut</system:String>
|
||||
<system:String x:Key="customShortcutExpansion">Expanded</system:String>
|
||||
<system:String x:Key="builtinShortcutDescription">Description</system:String>
|
||||
<system:String x:Key="delete">Delete</system:String>
|
||||
<system:String x:Key="edit">Edit</system:String>
|
||||
<system:String x:Key="add">Add</system:String>
|
||||
<system:String x:Key="pleaseSelectAnItem">Please select an item</system:String>
|
||||
<system:String x:Key="deleteCustomHotkeyWarning">Are you sure you want to delete {0} plugin hotkey?</system:String>
|
||||
<system:String x:Key="deleteCustomShortcutWarning">Are you sure you want to delete shortcut: {0} with expansion {1}?</system:String>
|
||||
<system:String x:Key="shortcut_clipboard_description">Get text from clipboard.</system:String>
|
||||
<system:String x:Key="queryWindowShadowEffect">Query window shadow effect</system:String>
|
||||
<system:String x:Key="shadowEffectCPUUsage">Shadow effect has a substantial usage of GPU. Not recommended if your computer performance is limited.</system:String>
|
||||
<system:String x:Key="windowWidthSize">Window Width Size</system:String>
|
||||
<system:String x:Key="windowWidthSizeToolTip">You can also quickly adjust this by using Ctrl+[ and Ctrl+].</system:String>
|
||||
<system:String x:Key="useGlyphUI">Use Segoe Fluent Icons</system:String>
|
||||
<system:String x:Key="useGlyphUIEffect">Use Segoe Fluent Icons for query results where supported</system:String>
|
||||
<system:String x:Key="flowlauncherPressHotkey">Press Key</system:String>
|
||||
|
||||
<!-- Setting Proxy -->
|
||||
<system:String x:Key="proxy">HTTP Proxy</system:String>
|
||||
|
|
@ -179,6 +188,7 @@
|
|||
<system:String x:Key="github">Github</system:String>
|
||||
<system:String x:Key="docs">Docs</system:String>
|
||||
<system:String x:Key="version">Version</system:String>
|
||||
<system:String x:Key="icons">Icons</system:String>
|
||||
<system:String x:Key="about_activate_times">You have activated Flow Launcher {0} times</system:String>
|
||||
<system:String x:Key="checkUpdates">Check for Updates</system:String>
|
||||
<system:String x:Key="newVersionTips">New version {0} is available, would you like to restart Flow Launcher to use the update?</system:String>
|
||||
|
|
@ -241,6 +251,12 @@
|
|||
<system:String x:Key="invalidPluginHotkey">Invalid plugin hotkey</system:String>
|
||||
<system:String x:Key="update">Update</system:String>
|
||||
|
||||
<!-- Custom Query Shortcut Dialog -->
|
||||
<system:String x:Key="customeQueryShortcutTitle">Custom Query Shortcut</system:String>
|
||||
<system:String x:Key="customeQueryShortcutTips">Enter a shortcut that automatically expands to the specified query.</system:String>
|
||||
<system:String x:Key="duplicateShortcut">Shortcut already exists, please enter a new Shortcut or edit the existing one.</system:String>
|
||||
<system:String x:Key="emptyShortcut">Shortcut and/or its expansion is empty.</system:String>
|
||||
|
||||
<!-- Hotkey Control -->
|
||||
<system:String x:Key="hotkeyUnavailable">Hotkey Unavailable</system:String>
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
xmlns:flowlauncher="clr-namespace:Flow.Launcher"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
|
||||
xmlns:ui="http://schemas.modernwpf.com/2019"
|
||||
xmlns:vm="clr-namespace:Flow.Launcher.ViewModel"
|
||||
Name="FlowMainWindow"
|
||||
Title="Flow Launcher"
|
||||
|
|
@ -80,30 +81,26 @@
|
|||
Command="{Binding LoadContextMenuCommand}" />
|
||||
<KeyBinding Key="Left"
|
||||
Command="{Binding EscCommand}" />
|
||||
<KeyBinding
|
||||
Key="H"
|
||||
<KeyBinding Key="H"
|
||||
Command="{Binding LoadHistoryCommand}"
|
||||
Modifiers="Ctrl" />
|
||||
<KeyBinding Key="Right" Command="{Binding LoadContextMenuCommand}" />
|
||||
<KeyBinding Key="Left" Command="{Binding EscCommand}" />
|
||||
<KeyBinding
|
||||
Key="OemCloseBrackets"
|
||||
<KeyBinding Key="Right"
|
||||
Command="{Binding LoadContextMenuCommand}" />
|
||||
<KeyBinding Key="Left"
|
||||
Command="{Binding EscCommand}" />
|
||||
<KeyBinding Key="OemCloseBrackets"
|
||||
Command="{Binding IncreaseWidthCommand}"
|
||||
Modifiers="Control" />
|
||||
<KeyBinding
|
||||
Key="OemOpenBrackets"
|
||||
<KeyBinding Key="OemOpenBrackets"
|
||||
Command="{Binding DecreaseWidthCommand}"
|
||||
Modifiers="Control" />
|
||||
<KeyBinding
|
||||
Key="OemPlus"
|
||||
<KeyBinding Key="OemPlus"
|
||||
Command="{Binding IncreaseMaxResultCommand}"
|
||||
Modifiers="Control" />
|
||||
<KeyBinding
|
||||
Key="OemMinus"
|
||||
<KeyBinding Key="OemMinus"
|
||||
Command="{Binding DecreaseMaxResultCommand}"
|
||||
Modifiers="Control" />
|
||||
<KeyBinding
|
||||
Key="H"
|
||||
<KeyBinding Key="H"
|
||||
Command="{Binding LoadHistoryCommand}"
|
||||
Modifiers="Ctrl" />
|
||||
<KeyBinding Key="Enter"
|
||||
|
|
@ -195,32 +192,51 @@
|
|||
<TextBox.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Command="ApplicationCommands.Cut"
|
||||
Header="{DynamicResource cut}" />
|
||||
Header="{DynamicResource cut}">
|
||||
<MenuItem.Icon>
|
||||
<ui:FontIcon Glyph="" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Command="ApplicationCommands.Copy"
|
||||
Header="{DynamicResource copy}" />
|
||||
Header="{DynamicResource copy}">
|
||||
<MenuItem.Icon>
|
||||
<ui:FontIcon Glyph="" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Command="ApplicationCommands.Paste"
|
||||
Header="{DynamicResource paste}" />
|
||||
Header="{DynamicResource paste}">
|
||||
<MenuItem.Icon>
|
||||
<ui:FontIcon Glyph="" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator Margin="0"
|
||||
Padding="0,4,0,4"
|
||||
Background="{DynamicResource ContextSeparator}" />
|
||||
<MenuItem Click="OnContextMenusForSettingsClick"
|
||||
Header="{DynamicResource flowlauncher_settings}" />
|
||||
Header="{DynamicResource flowlauncher_settings}">
|
||||
<MenuItem.Icon>
|
||||
<ui:FontIcon Glyph="" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Command="{Binding EscCommand}"
|
||||
Header="{DynamicResource closeWindow}" />
|
||||
Header="{DynamicResource closeWindow}">
|
||||
<MenuItem.Icon>
|
||||
<ui:FontIcon Glyph="" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</ContextMenu>
|
||||
</TextBox.ContextMenu>
|
||||
</TextBox>
|
||||
|
||||
<StackPanel x:Name="ClockPanel"
|
||||
Style="{DynamicResource ClockPanel}"
|
||||
IsHitTestVisible="False">
|
||||
<TextBlock
|
||||
Visibility="{Binding Settings.UseDate, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{DynamicResource DateBox}"
|
||||
Text="{Binding DateText}" />
|
||||
IsHitTestVisible="False"
|
||||
Style="{DynamicResource ClockPanel}">
|
||||
<TextBlock Style="{DynamicResource DateBox}"
|
||||
Text="{Binding DateText}"
|
||||
Visibility="{Binding Settings.UseDate, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
<TextBlock Style="{DynamicResource ClockBox}"
|
||||
Visibility="{Binding Settings.UseClock, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Text="{Binding ClockText}"/>
|
||||
Text="{Binding ClockText}"
|
||||
Visibility="{Binding Settings.UseClock, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
</StackPanel>
|
||||
|
||||
<Canvas Style="{DynamicResource SearchIconPosition}">
|
||||
|
|
@ -299,7 +315,8 @@
|
|||
<ContentControl>
|
||||
<flowlauncher:ResultListBox x:Name="ResultListBox"
|
||||
DataContext="{Binding Results}"
|
||||
PreviewMouseDown="OnPreviewMouseButtonDown" />
|
||||
LeftClickResultCommand="{Binding LeftClickResultCommand}"
|
||||
RightClickResultCommand="{Binding RightClickResultCommand}" />
|
||||
</ContentControl>
|
||||
</Border>
|
||||
<Border Style="{DynamicResource WindowRadius}">
|
||||
|
|
@ -316,7 +333,8 @@
|
|||
<ContentControl>
|
||||
<flowlauncher:ResultListBox x:Name="ContextMenu"
|
||||
DataContext="{Binding ContextMenu}"
|
||||
PreviewMouseDown="OnPreviewMouseButtonDown" />
|
||||
LeftClickResultCommand="{Binding LeftClickResultCommand}"
|
||||
RightClickResultCommand="{Binding RightClickResultCommand}" />
|
||||
</ContentControl>
|
||||
</Border>
|
||||
<Border Style="{DynamicResource WindowRadius}">
|
||||
|
|
@ -333,7 +351,8 @@
|
|||
<ContentControl>
|
||||
<flowlauncher:ResultListBox x:Name="History"
|
||||
DataContext="{Binding History}"
|
||||
PreviewMouseDown="OnPreviewMouseButtonDown" />
|
||||
LeftClickResultCommand="{Binding LeftClickResultCommand}"
|
||||
RightClickResultCommand="{Binding RightClickResultCommand}" />
|
||||
</ContentControl>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
|
|
|||
|
|
@ -20,8 +20,16 @@ using Flow.Launcher.Infrastructure;
|
|||
using System.Windows.Media;
|
||||
using Flow.Launcher.Infrastructure.Hotkey;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using System.Text;
|
||||
using DataObject = System.Windows.DataObject;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.IO;
|
||||
using System.Windows.Threading;
|
||||
using System.Windows.Data;
|
||||
using ModernWpf.Controls;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms.Design.Behavior;
|
||||
|
||||
namespace Flow.Launcher
|
||||
{
|
||||
|
|
@ -186,11 +194,12 @@ namespace Flow.Launcher
|
|||
private void UpdateNotifyIconText()
|
||||
{
|
||||
var menu = contextMenu;
|
||||
((MenuItem)menu.Items[1]).Header = InternationalizationManager.Instance.GetTranslation("iconTrayOpen") + " (" + _settings.Hotkey + ")";
|
||||
((MenuItem)menu.Items[2]).Header = InternationalizationManager.Instance.GetTranslation("GameMode");
|
||||
((MenuItem)menu.Items[3]).Header = InternationalizationManager.Instance.GetTranslation("PositionReset");
|
||||
((MenuItem)menu.Items[4]).Header = InternationalizationManager.Instance.GetTranslation("iconTraySettings");
|
||||
((MenuItem)menu.Items[5]).Header = InternationalizationManager.Instance.GetTranslation("iconTrayExit");
|
||||
((MenuItem)menu.Items[0]).Header = InternationalizationManager.Instance.GetTranslation("iconTrayOpen") + " (" + _settings.Hotkey + ")";
|
||||
((MenuItem)menu.Items[1]).Header = InternationalizationManager.Instance.GetTranslation("GameMode");
|
||||
((MenuItem)menu.Items[2]).Header = InternationalizationManager.Instance.GetTranslation("PositionReset");
|
||||
((MenuItem)menu.Items[3]).Header = InternationalizationManager.Instance.GetTranslation("iconTraySettings");
|
||||
((MenuItem)menu.Items[4]).Header = InternationalizationManager.Instance.GetTranslation("iconTrayExit");
|
||||
|
||||
}
|
||||
|
||||
private void InitializeNotifyIcon()
|
||||
|
|
@ -202,31 +211,35 @@ namespace Flow.Launcher
|
|||
Visible = !_settings.HideNotifyIcon
|
||||
};
|
||||
contextMenu = new ContextMenu();
|
||||
|
||||
var header = new MenuItem
|
||||
{
|
||||
Header = "Flow Launcher",
|
||||
IsEnabled = false
|
||||
};
|
||||
var openIcon = new FontIcon { Glyph = "\ue71e" };
|
||||
var open = new MenuItem
|
||||
{
|
||||
Header = InternationalizationManager.Instance.GetTranslation("iconTrayOpen") + " (" +_settings.Hotkey + ")"
|
||||
Header = InternationalizationManager.Instance.GetTranslation("iconTrayOpen") + " (" + _settings.Hotkey + ")",
|
||||
Icon = openIcon
|
||||
};
|
||||
var gamemodeIcon = new FontIcon { Glyph = "\ue7fc" };
|
||||
var gamemode = new MenuItem
|
||||
{
|
||||
Header = InternationalizationManager.Instance.GetTranslation("GameMode")
|
||||
Header = InternationalizationManager.Instance.GetTranslation("GameMode"),
|
||||
Icon = gamemodeIcon
|
||||
};
|
||||
var positionresetIcon = new FontIcon { Glyph = "\ue73f" };
|
||||
var positionreset = new MenuItem
|
||||
{
|
||||
Header = InternationalizationManager.Instance.GetTranslation("PositionReset")
|
||||
Header = InternationalizationManager.Instance.GetTranslation("PositionReset"),
|
||||
Icon = positionresetIcon
|
||||
};
|
||||
var settingsIcon = new FontIcon { Glyph = "\ue713" };
|
||||
var settings = new MenuItem
|
||||
{
|
||||
Header = InternationalizationManager.Instance.GetTranslation("iconTraySettings")
|
||||
Header = InternationalizationManager.Instance.GetTranslation("iconTraySettings"),
|
||||
Icon = settingsIcon
|
||||
};
|
||||
var exitIcon = new FontIcon { Glyph = "\ue7e8" };
|
||||
var exit = new MenuItem
|
||||
{
|
||||
Header = InternationalizationManager.Instance.GetTranslation("iconTrayExit")
|
||||
Header = InternationalizationManager.Instance.GetTranslation("iconTrayExit"),
|
||||
Icon = exitIcon
|
||||
};
|
||||
|
||||
open.Click += (o, e) => _viewModel.ToggleFlowLauncher();
|
||||
|
|
@ -234,7 +247,6 @@ namespace Flow.Launcher
|
|||
positionreset.Click += (o, e) => PositionReset();
|
||||
settings.Click += (o, e) => App.API.OpenSettingDialog();
|
||||
exit.Click += (o, e) => Close();
|
||||
contextMenu.Items.Add(header);
|
||||
contextMenu.Items.Add(open);
|
||||
gamemode.ToolTip = InternationalizationManager.Instance.GetTranslation("GameModeToolTip");
|
||||
positionreset.ToolTip = InternationalizationManager.Instance.GetTranslation("PositionResetToolTip");
|
||||
|
|
@ -375,28 +387,6 @@ namespace Flow.Launcher
|
|||
if (e.ChangedButton == MouseButton.Left) DragMove();
|
||||
}
|
||||
|
||||
private void OnPreviewMouseButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender != null && e.OriginalSource != null)
|
||||
{
|
||||
var r = (ResultListBox)sender;
|
||||
var d = (DependencyObject)e.OriginalSource;
|
||||
var item = ItemsControl.ContainerFromElement(r, d) as ListBoxItem;
|
||||
var result = (ResultViewModel)item?.DataContext;
|
||||
if (result != null)
|
||||
{
|
||||
if (e.ChangedButton == MouseButton.Left)
|
||||
{
|
||||
_viewModel.OpenResultCommand.Execute(null);
|
||||
}
|
||||
else if (e.ChangedButton == MouseButton.Right)
|
||||
{
|
||||
_viewModel.LoadContextMenuCommand.Execute(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPreviewDragOver(object sender, DragEventArgs e)
|
||||
{
|
||||
e.Handled = true;
|
||||
|
|
|
|||
|
|
@ -1407,7 +1407,8 @@
|
|||
Padding="{TemplateBinding Padding}"
|
||||
BorderBrush="{DynamicResource ButtonInsideBorder}"
|
||||
BorderThickness="{DynamicResource CustomButtonInsideBorderThickness}"
|
||||
CornerRadius="4">
|
||||
CornerRadius="4"
|
||||
SnapsToDevicePixels="True">
|
||||
<ContentPresenter
|
||||
x:Name="ContentPresenter"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
|
|
@ -2044,31 +2045,43 @@
|
|||
<Border Padding="{TemplateBinding Padding}">
|
||||
<Grid Background="Transparent" SnapsToDevicePixels="False">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="19" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Ellipse
|
||||
x:Name="circle"
|
||||
Width="19"
|
||||
Height="19"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Stroke="Transparent" />
|
||||
<Path
|
||||
x:Name="arrow"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Data="M 1,1.5 L 4.5,5 L 8,1.5"
|
||||
SnapsToDevicePixels="false"
|
||||
Stroke="#666"
|
||||
StrokeThickness="1" />
|
||||
<ContentPresenter
|
||||
Grid.Column="1"
|
||||
Margin="4,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Grid.Column="0"
|
||||
Margin="0,0,0,0"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Content="{TemplateBinding Content}"
|
||||
RecognizesAccessKey="True"
|
||||
SnapsToDevicePixels="True" />
|
||||
<Grid
|
||||
x:Name="ChevronGrid"
|
||||
Grid.Column="1"
|
||||
Margin="0"
|
||||
VerticalAlignment="Center"
|
||||
Background="Transparent"
|
||||
RenderTransformOrigin="0.5, 0.5">
|
||||
<Grid.RenderTransform>
|
||||
<RotateTransform Angle="0" />
|
||||
</Grid.RenderTransform>
|
||||
<Ellipse
|
||||
x:Name="circle"
|
||||
Width="19"
|
||||
Height="19"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Stroke="Transparent" />
|
||||
<Path
|
||||
x:Name="arrow"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Data="M 1,1.5 L 4.5,5 L 8,1.5"
|
||||
SnapsToDevicePixels="false"
|
||||
Stroke="#666"
|
||||
StrokeThickness="1" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
|
|
@ -2077,12 +2090,12 @@
|
|||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="true">
|
||||
<Setter TargetName="circle" Property="Stroke" Value="Transparent" />
|
||||
<Setter TargetName="arrow" Property="Stroke" Value="#222" />
|
||||
<Setter TargetName="arrow" Property="Stroke" Value="{DynamicResource Color05B}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="true">
|
||||
<Setter TargetName="circle" Property="Stroke" Value="Transparent" />
|
||||
<Setter TargetName="circle" Property="StrokeThickness" Value="1.5" />
|
||||
<Setter TargetName="arrow" Property="Stroke" Value="#FF003366" />
|
||||
<Setter TargetName="arrow" Property="Stroke" Value="{DynamicResource Color17B}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
|
|
@ -2110,7 +2123,7 @@
|
|||
x:Name="HeaderSite"
|
||||
MinWidth="0"
|
||||
MinHeight="0"
|
||||
Margin="18,0,0,0"
|
||||
Margin="18,0,18,0"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
|
|
@ -2127,19 +2140,62 @@
|
|||
Foreground="{TemplateBinding Foreground}"
|
||||
IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Style="{StaticResource ExpanderDownHeaderStyle}" />
|
||||
<ContentPresenter
|
||||
x:Name="ExpandSite"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
DockPanel.Dock="Bottom"
|
||||
Focusable="false"
|
||||
Visibility="Collapsed" />
|
||||
<Border x:Name="ContentPresenterBorder">
|
||||
<ContentPresenter
|
||||
x:Name="ExpandSite"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
DockPanel.Dock="Bottom"
|
||||
Focusable="false" />
|
||||
<Border.LayoutTransform>
|
||||
<ScaleTransform ScaleY="0" />
|
||||
</Border.LayoutTransform>
|
||||
</Border>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsExpanded" Value="true">
|
||||
<Setter TargetName="ExpandSite" Property="Visibility" Value="Visible" />
|
||||
<Setter TargetName="ContentPresenterBorder" Property="BorderThickness" Value="0,1,0,0" />
|
||||
<Trigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetName="ContentPresenterBorder"
|
||||
Storyboard.TargetProperty="(Border.LayoutTransform).(ScaleTransform.ScaleY)"
|
||||
From="0.0"
|
||||
To="1.0"
|
||||
Duration="00:00:00.00" />
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetName="ContentPresenterBorder"
|
||||
Storyboard.TargetProperty="(Border.Opacity)"
|
||||
From="0.0"
|
||||
To="1.0"
|
||||
Duration="00:00:00.00" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</Trigger.EnterActions>
|
||||
<Trigger.ExitActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetName="ContentPresenterBorder"
|
||||
Storyboard.TargetProperty="(Border.LayoutTransform).(ScaleTransform.ScaleY)"
|
||||
From="1.0"
|
||||
To="0"
|
||||
Duration="00:00:00.00" />
|
||||
<!-- Animation 00:00:00.167 -->
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetName="ContentPresenterBorder"
|
||||
Storyboard.TargetProperty="(Border.Opacity)"
|
||||
From="1.0"
|
||||
To="0.0"
|
||||
Duration="00:00:00.00" />
|
||||
<!-- Animation 00:00:00.167 -->
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</Trigger.ExitActions>
|
||||
</Trigger>
|
||||
<Trigger Property="ExpandDirection" Value="Right">
|
||||
<Setter TargetName="ExpandSite" Property="DockPanel.Dock" Value="Right" />
|
||||
|
|
@ -2672,73 +2728,491 @@
|
|||
</Style>
|
||||
<!--#endregion-->
|
||||
|
||||
<Style TargetType="{x:Type MenuItem}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
|
||||
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource Color05B}" />
|
||||
<Setter Property="Stylus.IsFlicksEnabled" Value="False" />
|
||||
<Setter Property="OverridesDefaultStyle" Value="True" />
|
||||
|
||||
<system:Double x:Key="MenuBarHeight">40</system:Double>
|
||||
<Thickness x:Key="MenuFlyoutScrollerMargin">4,4,4,4</Thickness>
|
||||
<Thickness x:Key="MenuFlyoutItemRevealBorderThickness">1</Thickness>
|
||||
<Thickness x:Key="ToggleMenuFlyoutItemRevealBorderThickness">1</Thickness>
|
||||
<Thickness x:Key="MenuFlyoutSubItemRevealBorderThickness">1</Thickness>
|
||||
|
||||
<ui:SharedSizeGroupConverter x:Key="SharedSizeGroupConverter" />
|
||||
|
||||
<Style x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type FrameworkElement}, ResourceId=MenuScrollViewer}" TargetType="ScrollViewer">
|
||||
<Setter Property="HorizontalScrollBarVisibility" Value="Disabled" />
|
||||
<Setter Property="VerticalScrollBarVisibility" Value="Auto" />
|
||||
<Setter Property="PanningMode" Value="VerticalOnly" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="DefaultMenuItemSeparatorStyle" TargetType="Separator">
|
||||
<Setter Property="Background" Value="{DynamicResource MenuFlyoutSeparatorBackground}" />
|
||||
<Setter Property="Padding" Value="{DynamicResource MenuFlyoutSeparatorThemePadding}" />
|
||||
<Setter Property="SnapsToDevicePixels" Value="true" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type MenuItem}">
|
||||
<Border
|
||||
Name="bdr"
|
||||
Padding="24,6,24,6"
|
||||
Background="Transparent">
|
||||
<TextBlock
|
||||
Name="Menu"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Foreground="{DynamicResource Color05B}"
|
||||
Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Header}" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsVisible" Value="True">
|
||||
<Setter TargetName="bdr" Property="Background" Value="{DynamicResource CustomContextBackground}" />
|
||||
<Setter TargetName="Menu" Property="Foreground" Value="{DynamicResource Color05B}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="bdr" Property="Background" Value="{DynamicResource CustomContextHover}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="Menu" Property="Foreground" Value="{DynamicResource CustomContextDisabled}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
<ControlTemplate TargetType="Separator">
|
||||
<Rectangle
|
||||
Height="{DynamicResource MenuFlyoutSeparatorThemeHeight}"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
Fill="{TemplateBinding Background}" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type ContextMenu}">
|
||||
<Style
|
||||
x:Key="{x:Static MenuItem.SeparatorStyleKey}"
|
||||
BasedOn="{StaticResource DefaultMenuItemSeparatorStyle}"
|
||||
TargetType="Separator" />
|
||||
|
||||
<ControlTemplate x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=TopLevelItemTemplateKey}" TargetType="{x:Type MenuItem}">
|
||||
<Grid
|
||||
x:Name="ContentRoot"
|
||||
Background="{TemplateBinding Background}"
|
||||
SnapsToDevicePixels="True">
|
||||
<Border
|
||||
x:Name="Background"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}" />
|
||||
|
||||
<ContentPresenter
|
||||
Margin="12,0,12,0"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
ContentSource="Header"
|
||||
RecognizesAccessKey="True"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Background" Property="Background" Value="{DynamicResource MenuBarItemBackgroundPointerOver}" />
|
||||
<Setter TargetName="Background" Property="BorderBrush" Value="{DynamicResource MenuBarItemBorderBrushPointerOver}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="Background" Property="Background" Value="{DynamicResource MenuBarItemBackgroundPressed}" />
|
||||
<Setter TargetName="Background" Property="BorderBrush" Value="{DynamicResource MenuBarItemBorderBrushPressed}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
|
||||
<ControlTemplate x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=TopLevelHeaderTemplateKey}" TargetType="{x:Type MenuItem}">
|
||||
<Grid
|
||||
x:Name="ContentRoot"
|
||||
Background="{TemplateBinding Background}"
|
||||
SnapsToDevicePixels="True">
|
||||
<Border
|
||||
x:Name="Background"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}" />
|
||||
|
||||
<ContentPresenter
|
||||
Margin="12,0,12,0"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
ContentSource="Header"
|
||||
RecognizesAccessKey="True"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
|
||||
<ui:MenuPopup
|
||||
x:Name="PART_Popup"
|
||||
AllowsTransparency="true"
|
||||
Focusable="false"
|
||||
IsOpen="{Binding IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Placement="Bottom"
|
||||
PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}">
|
||||
<ui:ThemeShadowChrome CornerRadius="{TemplateBinding ui:ControlHelper.CornerRadius}" IsShadowEnabled="{DynamicResource {x:Static SystemParameters.DropShadowKey}}">
|
||||
<Border
|
||||
x:Name="SubMenuRoot"
|
||||
Background="{DynamicResource MenuFlyoutPresenterBackground}"
|
||||
CornerRadius="{TemplateBinding ui:ControlHelper.CornerRadius}">
|
||||
<Grid>
|
||||
<ScrollViewer
|
||||
x:Name="SubMenuScrollViewer"
|
||||
MinWidth="{DynamicResource FlyoutThemeMinWidth}"
|
||||
Margin="{DynamicResource MenuFlyoutPresenterThemePadding}"
|
||||
Style="{DynamicResource {ComponentResourceKey ResourceId=MenuScrollViewer,
|
||||
TypeInTargetAssembly={x:Type FrameworkElement}}}">
|
||||
<ItemsPresenter
|
||||
Grid.IsSharedSizeScope="true"
|
||||
KeyboardNavigation.DirectionalNavigation="Cycle"
|
||||
KeyboardNavigation.TabNavigation="Cycle"
|
||||
RenderOptions.ClearTypeHint="Enabled"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
</ScrollViewer>
|
||||
<Border
|
||||
x:Name="SubMenuBorder"
|
||||
BorderBrush="{DynamicResource MenuFlyoutPresenterBorderBrush}"
|
||||
BorderThickness="{DynamicResource MenuFlyoutPresenterBorderThemeThickness}"
|
||||
CornerRadius="{TemplateBinding ui:ControlHelper.CornerRadius}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</ui:ThemeShadowChrome>
|
||||
</ui:MenuPopup>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsSuspendingPopupAnimation" Value="true">
|
||||
<Setter TargetName="PART_Popup" Property="PopupAnimation" Value="None" />
|
||||
</Trigger>
|
||||
<Trigger SourceName="PART_Popup" Property="IsSuspendingAnimation" Value="true">
|
||||
<Setter TargetName="PART_Popup" Property="PopupAnimation" Value="None" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Background" Property="Background" Value="{DynamicResource MenuBarItemBackgroundPointerOver}" />
|
||||
<Setter TargetName="Background" Property="BorderBrush" Value="{DynamicResource MenuBarItemBorderBrushPointerOver}" />
|
||||
</Trigger>
|
||||
<!-- Selected -->
|
||||
<Trigger Property="IsSubmenuOpen" Value="True">
|
||||
<Setter TargetName="Background" Property="Background" Value="{DynamicResource MenuBarItemBackgroundSelected}" />
|
||||
<Setter TargetName="Background" Property="BorderBrush" Value="{DynamicResource MenuBarItemBorderBrushSelected}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="Background" Property="Background" Value="{DynamicResource MenuBarItemBackgroundPressed}" />
|
||||
<Setter TargetName="Background" Property="BorderBrush" Value="{DynamicResource MenuBarItemBorderBrushPressed}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
|
||||
<!-- DefaultMenuFlyoutSubItemStyle -->
|
||||
<ControlTemplate x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=SubmenuItemTemplateKey}" TargetType="{x:Type MenuItem}">
|
||||
<ControlTemplate.Resources>
|
||||
<StreamGeometry x:Key="CheckMark">F1 M 17.939453 5.439453 L 7.5 15.888672 L 2.060547 10.439453 L 2.939453 9.560547 L 7.5 14.111328 L 17.060547 4.560547 Z</StreamGeometry>
|
||||
</ControlTemplate.Resources>
|
||||
<Border
|
||||
x:Name="LayoutRoot"
|
||||
Height="Auto"
|
||||
Margin="5,2,5,2"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding ui:ControlHelper.CornerRadius}"
|
||||
SnapsToDevicePixels="true"
|
||||
TextElement.Foreground="{DynamicResource Color05B}"
|
||||
UseLayoutRounding="True">
|
||||
<Grid x:Name="AnimationRoot">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="{TemplateBinding Visibility, Converter={StaticResource SharedSizeGroupConverter}, ConverterParameter=MenuItemCheckColumnGroup}" />
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="{TemplateBinding Visibility, Converter={StaticResource SharedSizeGroupConverter}, ConverterParameter=MenuItemIconColumnGroup}" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ui:FontIconFallback
|
||||
x:Name="CheckGlyph"
|
||||
Margin="0,0,16,0"
|
||||
Data="{StaticResource CheckMark}"
|
||||
FontFamily="{DynamicResource SymbolThemeFontFamily}"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource ToggleMenuFlyoutItemCheckGlyphForeground}"
|
||||
Opacity="0"
|
||||
Visibility="Collapsed" />
|
||||
<Viewbox
|
||||
x:Name="IconRoot"
|
||||
Grid.Column="1"
|
||||
Width="16"
|
||||
Height="16"
|
||||
Margin="0,1,12,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Stretch"
|
||||
TextBlock.FontFamily="/Resources/#Segoe Fluent Icons">
|
||||
<ContentPresenter
|
||||
x:Name="IconContent"
|
||||
ContentSource="Icon"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
</Viewbox>
|
||||
<ContentPresenter
|
||||
x:Name="ContentPresenter"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
ContentSource="Header"
|
||||
RecognizesAccessKey="True"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
||||
TextElement.Foreground="{DynamicResource Color05B}" />
|
||||
<TextBlock
|
||||
x:Name="KeyboardAcceleratorTextBlock"
|
||||
Grid.Column="3"
|
||||
Margin="24,4,0,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Foreground="{DynamicResource MenuFlyoutItemKeyboardAcceleratorTextForeground}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{TemplateBinding InputGestureText}"
|
||||
Visibility="Collapsed" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsVisible" Value="True">
|
||||
<Setter TargetName="LayoutRoot" Property="TextElement.Foreground" Value="{DynamicResource Color05B}" />
|
||||
<Setter TargetName="ContentPresenter" Property="TextElement.Foreground" Value="{DynamicResource Color05B}" />
|
||||
</Trigger>
|
||||
<Trigger Property="Icon" Value="{x:Null}">
|
||||
<Setter TargetName="IconRoot" Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
<Trigger Property="InputGestureText" Value="">
|
||||
<Setter TargetName="KeyboardAcceleratorTextBlock" Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter TargetName="CheckGlyph" Property="Opacity" Value="1" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsCheckable" Value="True">
|
||||
<Setter TargetName="CheckGlyph" Property="Visibility" Value="Visible" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsHighlighted" Value="True">
|
||||
<Setter TargetName="LayoutRoot" Property="Background" Value="{DynamicResource CustomContextHover}" />
|
||||
<Setter TargetName="LayoutRoot" Property="TextElement.Foreground" Value="{DynamicResource Color05B}" />
|
||||
<Setter TargetName="ContentPresenter" Property="TextElement.Foreground" Value="{DynamicResource Color05B}" />
|
||||
<Setter TargetName="KeyboardAcceleratorTextBlock" Property="Foreground" Value="{DynamicResource MenuFlyoutItemKeyboardAcceleratorTextForegroundPointerOver}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="LayoutRoot" Property="Background" Value="{DynamicResource CustomContextClick}" />
|
||||
<Setter TargetName="LayoutRoot" Property="TextElement.Foreground" Value="{DynamicResource Color05B}" />
|
||||
<Setter TargetName="ContentPresenter" Property="TextElement.Foreground" Value="{DynamicResource Color05B}" />
|
||||
<Setter TargetName="KeyboardAcceleratorTextBlock" Property="Foreground" Value="{DynamicResource MenuFlyoutItemKeyboardAcceleratorTextForegroundPressed}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="LayoutRoot" Property="Background" Value="{DynamicResource MenuFlyoutItemBackgroundDisabled}" />
|
||||
<Setter TargetName="LayoutRoot" Property="TextElement.Foreground" Value="{DynamicResource MenuFlyoutItemForegroundDisabled}" />
|
||||
<Setter TargetName="ContentPresenter" Property="TextElement.Foreground" Value="{DynamicResource MenuFlyoutItemForegroundDisabled}" />
|
||||
<Setter TargetName="KeyboardAcceleratorTextBlock" Property="Foreground" Value="{DynamicResource MenuFlyoutItemKeyboardAcceleratorTextForegroundDisabled}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
|
||||
<!-- DefaultMenuFlyoutItemStyle -->
|
||||
<ControlTemplate x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=SubmenuHeaderTemplateKey}" TargetType="{x:Type MenuItem}">
|
||||
<ControlTemplate.Resources>
|
||||
<StreamGeometry x:Key="ChevronRight">M 5.029297 19.091797 L 14.111328 10 L 5.029297 0.908203 L 5.908203 0.029297 L 15.888672 10 L 5.908203 19.970703 Z</StreamGeometry>
|
||||
</ControlTemplate.Resources>
|
||||
<Border
|
||||
x:Name="LayoutRoot"
|
||||
Margin="{DynamicResource MenuFlyoutItemMargin}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding ui:ControlHelper.CornerRadius}"
|
||||
SnapsToDevicePixels="True">
|
||||
<Grid x:Name="AnimationRoot">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="MenuItemCheckColumnGroup" />
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="MenuItemIconColumnGroup" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Viewbox
|
||||
x:Name="IconRoot"
|
||||
Grid.Column="1"
|
||||
Width="16"
|
||||
Height="16"
|
||||
Margin="0,0,12,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center">
|
||||
<ContentPresenter
|
||||
x:Name="IconContent"
|
||||
ContentSource="Icon"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
</Viewbox>
|
||||
<ContentPresenter
|
||||
x:Name="ContentPresenter"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
ContentSource="Header"
|
||||
RecognizesAccessKey="True"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
||||
TextElement.Foreground="{TemplateBinding Foreground}" />
|
||||
<ui:FontIconFallback
|
||||
x:Name="SubItemChevron"
|
||||
Grid.Column="3"
|
||||
Margin="{DynamicResource MenuFlyoutItemChevronMargin}"
|
||||
Data="{StaticResource ChevronRight}"
|
||||
FontFamily="{DynamicResource SymbolThemeFontFamily}"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource MenuFlyoutSubItemChevron}">
|
||||
<UIElement.RenderTransform>
|
||||
<TranslateTransform Y="1" />
|
||||
</UIElement.RenderTransform>
|
||||
</ui:FontIconFallback>
|
||||
<Popup
|
||||
x:Name="PART_Popup"
|
||||
AllowsTransparency="true"
|
||||
Focusable="false"
|
||||
IsOpen="{Binding IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Placement="Right"
|
||||
PlacementTarget="{Binding ElementName=LayoutRoot}"
|
||||
PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}">
|
||||
<Popup.PlacementRectangle>
|
||||
<MultiBinding>
|
||||
<MultiBinding.Converter>
|
||||
<ui:PlacementRectangleConverter Margin="4,-1" />
|
||||
</MultiBinding.Converter>
|
||||
<Binding ElementName="LayoutRoot" Path="ActualWidth" />
|
||||
<Binding ElementName="LayoutRoot" Path="ActualHeight" />
|
||||
</MultiBinding>
|
||||
</Popup.PlacementRectangle>
|
||||
<ui:ThemeShadowChrome CornerRadius="{TemplateBinding ui:ControlHelper.CornerRadius}" IsShadowEnabled="{DynamicResource {x:Static SystemParameters.DropShadowKey}}">
|
||||
<Border
|
||||
x:Name="SubMenuRoot"
|
||||
Background="{DynamicResource MenuFlyoutPresenterBackground}"
|
||||
CornerRadius="{TemplateBinding ui:ControlHelper.CornerRadius}">
|
||||
<Grid>
|
||||
<ScrollViewer
|
||||
x:Name="SubMenuScrollViewer"
|
||||
MinWidth="{DynamicResource FlyoutThemeMinWidth}"
|
||||
Margin="{DynamicResource MenuFlyoutPresenterThemePadding}"
|
||||
Style="{DynamicResource {ComponentResourceKey ResourceId=MenuScrollViewer,
|
||||
TypeInTargetAssembly={x:Type FrameworkElement}}}">
|
||||
<ItemsPresenter
|
||||
Grid.IsSharedSizeScope="true"
|
||||
KeyboardNavigation.DirectionalNavigation="Cycle"
|
||||
KeyboardNavigation.TabNavigation="Cycle"
|
||||
RenderOptions.ClearTypeHint="Enabled"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
</ScrollViewer>
|
||||
<Border
|
||||
x:Name="SubMenuBorder"
|
||||
BorderBrush="{DynamicResource MenuFlyoutPresenterBorderBrush}"
|
||||
BorderThickness="{DynamicResource MenuFlyoutPresenterBorderThemeThickness}"
|
||||
CornerRadius="{TemplateBinding ui:ControlHelper.CornerRadius}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</ui:ThemeShadowChrome>
|
||||
</Popup>
|
||||
</Grid>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsVisible" Value="True">
|
||||
<Setter TargetName="LayoutRoot" Property="TextElement.Foreground" Value="{DynamicResource Color05B}" />
|
||||
<Setter TargetName="ContentPresenter" Property="TextElement.Foreground" Value="{DynamicResource Color05B}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsSuspendingPopupAnimation" Value="true">
|
||||
<Setter TargetName="PART_Popup" Property="PopupAnimation" Value="None" />
|
||||
</Trigger>
|
||||
<Trigger Property="Icon" Value="{x:Null}">
|
||||
<Setter TargetName="IconRoot" Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsHighlighted" Value="True">
|
||||
<Setter TargetName="LayoutRoot" Property="Background" Value="{DynamicResource MenuFlyoutSubItemBackgroundPointerOver}" />
|
||||
<Setter TargetName="ContentPresenter" Property="TextElement.Foreground" Value="{DynamicResource MenuFlyoutSubItemForegroundPointerOver}" />
|
||||
<Setter TargetName="SubItemChevron" Property="Foreground" Value="{DynamicResource MenuFlyoutSubItemChevronPointerOver}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="LayoutRoot" Property="Background" Value="{DynamicResource MenuFlyoutSubItemBackgroundPressed}" />
|
||||
<Setter TargetName="ContentPresenter" Property="TextElement.Foreground" Value="{DynamicResource MenuFlyoutSubItemForegroundPressed}" />
|
||||
<Setter TargetName="SubItemChevron" Property="Foreground" Value="{DynamicResource MenuFlyoutSubItemChevronPressed}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsSubmenuOpen" Value="True">
|
||||
<Setter TargetName="LayoutRoot" Property="Background" Value="{DynamicResource MenuFlyoutSubItemBackgroundSubMenuOpened}" />
|
||||
<Setter TargetName="ContentPresenter" Property="TextElement.Foreground" Value="{DynamicResource MenuFlyoutSubItemForegroundSubMenuOpened}" />
|
||||
<Setter TargetName="SubItemChevron" Property="Foreground" Value="{DynamicResource MenuFlyoutSubItemChevronSubMenuOpened}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="LayoutRoot" Property="Background" Value="{DynamicResource MenuFlyoutSubItemBackgroundDisabled}" />
|
||||
<Setter TargetName="ContentPresenter" Property="TextElement.Foreground" Value="{DynamicResource MenuFlyoutSubItemForegroundDisabled}" />
|
||||
<Setter TargetName="SubItemChevron" Property="Foreground" Value="{DynamicResource MenuFlyoutSubItemChevronDisabled}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
|
||||
<Style x:Key="DefaultMenuItemStyle" TargetType="{x:Type MenuItem}">
|
||||
<Setter Property="OverridesDefaultStyle" Value="True" />
|
||||
<Setter Property="SnapsToDevicePixels" Value="True" />
|
||||
<Setter Property="Background" Value="{DynamicResource MenuFlyoutItemBackground}" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource MenuFlyoutItemForeground}" />
|
||||
<Setter Property="Padding" Value="{DynamicResource MenuFlyoutItemThemePaddingNarrow}" />
|
||||
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="ScrollViewer.PanningMode" Value="Both" />
|
||||
<Setter Property="Stylus.IsFlicksEnabled" Value="False" />
|
||||
<Setter Property="FocusVisualStyle" Value="{DynamicResource {x:Static SystemParameters.FocusVisualStyleKey}}" />
|
||||
<Setter Property="ui:FocusVisualHelper.UseSystemFocusVisuals" Value="{DynamicResource UseSystemFocusVisuals}" />
|
||||
<Setter Property="ui:ControlHelper.CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
|
||||
<Setter Property="Template" Value="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=SubmenuItemTemplateKey}}" />
|
||||
<Style.Triggers>
|
||||
<Trigger Property="Role" Value="TopLevelHeader">
|
||||
<Setter Property="Background" Value="{DynamicResource MenuBarItemBackground}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource MenuBarItemBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="{DynamicResource MenuBarItemBorderThickness}" />
|
||||
<Setter Property="Template" Value="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=TopLevelHeaderTemplateKey}}" />
|
||||
<Setter Property="IsTabStop" Value="True" />
|
||||
<Setter Property="Height" Value="{StaticResource MenuBarHeight}" />
|
||||
</Trigger>
|
||||
<Trigger Property="Role" Value="TopLevelItem">
|
||||
<Setter Property="Background" Value="{DynamicResource MenuBarItemBackground}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource MenuBarItemBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="{DynamicResource MenuBarItemBorderThickness}" />
|
||||
<Setter Property="Template" Value="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=TopLevelItemTemplateKey}}" />
|
||||
<Setter Property="IsTabStop" Value="True" />
|
||||
<Setter Property="Height" Value="{StaticResource MenuBarHeight}" />
|
||||
</Trigger>
|
||||
<Trigger Property="Role" Value="SubmenuHeader">
|
||||
<Setter Property="Background" Value="{DynamicResource MenuFlyoutSubItemBackground}" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource MenuFlyoutSubItemForeground}" />
|
||||
<Setter Property="Template" Value="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=SubmenuHeaderTemplateKey}}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsCheckable" Value="True">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="ui:FocusVisualHelper.UseSystemFocusVisuals" Value="True" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style BasedOn="{StaticResource DefaultMenuItemStyle}" TargetType="{x:Type MenuItem}" />
|
||||
|
||||
|
||||
|
||||
<Style TargetType="{x:Type ContextMenu}">
|
||||
<Setter Property="Background" Value="{DynamicResource CustomContextBackground}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource CustomContextBorder}" />
|
||||
<Setter Property="BorderThickness" Value="{DynamicResource MenuFlyoutPresenterBorderThemeThickness}" />
|
||||
<Setter Property="Padding" Value="{DynamicResource MenuFlyoutPresenterThemePadding}" />
|
||||
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
|
||||
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
|
||||
<Setter Property="FontStyle" Value="Normal" />
|
||||
<Setter Property="FontWeight" Value="Normal" />
|
||||
<Setter Property="Grid.IsSharedSizeScope" Value="true" />
|
||||
<Setter Property="HasDropShadow" Value="{DynamicResource {x:Static SystemParameters.DropShadowKey}}" />
|
||||
<!--<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />-->
|
||||
<!--<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />-->
|
||||
<!--<Setter Property="ScrollViewer.PanningMode" Value="VerticalOnly" />-->
|
||||
<Setter Property="Stylus.IsFlicksEnabled" Value="False" />
|
||||
<Setter Property="MaxWidth" Value="{DynamicResource FlyoutThemeMaxWidth}" />
|
||||
<Setter Property="MinHeight" Value="{DynamicResource MenuFlyoutThemeMinHeight}" />
|
||||
<Setter Property="ui:ControlHelper.CornerRadius" Value="{DynamicResource OverlayCornerRadius}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ContextMenu}">
|
||||
<Border
|
||||
x:Name="Border"
|
||||
Margin="5,4,5,12"
|
||||
Padding="0,4,0,4"
|
||||
Background="{DynamicResource CustomContextBackground}"
|
||||
BorderBrush="{DynamicResource CustomContextBorder}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8"
|
||||
UseLayoutRounding="True">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect
|
||||
BlurRadius="12"
|
||||
Direction="-90"
|
||||
Opacity=".15"
|
||||
ShadowDepth="6"
|
||||
Color="Black" />
|
||||
</Border.Effect>
|
||||
<StackPanel
|
||||
x:Name="Panel"
|
||||
ClipToBounds="True"
|
||||
IsItemsHost="True"
|
||||
Orientation="Vertical" />
|
||||
</Border>
|
||||
<ui:ThemeShadowChrome
|
||||
x:Name="Shdw"
|
||||
CornerRadius="{TemplateBinding ui:ControlHelper.CornerRadius}"
|
||||
IsShadowEnabled="{TemplateBinding HasDropShadow}"
|
||||
SnapsToDevicePixels="True">
|
||||
<Border
|
||||
x:Name="Border"
|
||||
Padding="0,4,0,4"
|
||||
Background="{DynamicResource CustomContextBackground}"
|
||||
BorderBrush="{DynamicResource CustomContextBorder}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8">
|
||||
<Grid>
|
||||
<ScrollViewer
|
||||
x:Name="ContextMenuScrollViewer"
|
||||
MinWidth="{DynamicResource FlyoutThemeMinWidth}"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
Style="{DynamicResource {ComponentResourceKey ResourceId=MenuScrollViewer,
|
||||
TypeInTargetAssembly={x:Type FrameworkElement}}}">
|
||||
<ItemsPresenter
|
||||
KeyboardNavigation.DirectionalNavigation="Cycle"
|
||||
RenderOptions.ClearTypeHint="Enabled"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
</ScrollViewer>
|
||||
<Border
|
||||
x:Name="ContextMenuBorder"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="{TemplateBinding ui:ControlHelper.CornerRadius}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</ui:ThemeShadowChrome>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsVisible" Value="true">
|
||||
<Setter TargetName="Border" Property="Background" Value="{DynamicResource CustomContextBackground}" />
|
||||
|
|
@ -2753,4 +3227,7 @@
|
|||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<ui:TextContextMenu x:Key="TextControlContextMenu" x:Shared="False" />
|
||||
|
||||
</ResourceDictionary>
|
||||
|
|
|
|||
|
|
@ -65,10 +65,13 @@
|
|||
|
||||
<SolidColorBrush x:Key="CustomContextBorder" Color="#1b1b1b" />
|
||||
<SolidColorBrush x:Key="CustomContextBackground" Color="#2b2b2b" />
|
||||
<SolidColorBrush x:Key="CustomContextHover" Color="#3c3c3c" />
|
||||
<SolidColorBrush x:Key="CustomContextHover" Color="#3a3a3a" />
|
||||
<SolidColorBrush x:Key="CustomContextClick" Color="#353535" />
|
||||
<SolidColorBrush x:Key="CustomContextDisabled" Color="#868686" />
|
||||
<SolidColorBrush x:Key="ContextSeparator" Color="#3c3c3c" />
|
||||
|
||||
<SolidColorBrush x:Key="HoverStoreGrid2">#272727</SolidColorBrush>
|
||||
|
||||
<Color x:Key="Color01">#202020</Color>
|
||||
<Color x:Key="Color02">#2b2b2b</Color>
|
||||
<Color x:Key="Color03">#1d1d1d</Color>
|
||||
|
|
|
|||
|
|
@ -58,10 +58,13 @@
|
|||
|
||||
<SolidColorBrush x:Key="CustomContextBorder" Color="#dadada" />
|
||||
<SolidColorBrush x:Key="CustomContextBackground" Color="#f2f2f2" />
|
||||
<SolidColorBrush x:Key="CustomContextHover" Color="#DBDBDB" />
|
||||
<SolidColorBrush x:Key="CustomContextHover" Color="#e4e4e4" />
|
||||
<SolidColorBrush x:Key="CustomContextClick" Color="#ececec" />
|
||||
<SolidColorBrush x:Key="CustomContextDisabled" Color="#868686" />
|
||||
<SolidColorBrush x:Key="ContextSeparator" Color="#dadada" />
|
||||
|
||||
<SolidColorBrush x:Key="HoverStoreGrid2">#f6f6f6</SolidColorBrush>
|
||||
|
||||
<Color x:Key="Color01">#f3f3f3</Color>
|
||||
<Color x:Key="Color02">#ffffff</Color>
|
||||
<Color x:Key="Color03">#e5e5e5</Color>
|
||||
|
|
|
|||
|
|
@ -25,12 +25,17 @@
|
|||
VirtualizingStackPanel.IsVirtualizing="True"
|
||||
VirtualizingStackPanel.VirtualizationMode="Standard"
|
||||
Visibility="{Binding Visbility}"
|
||||
mc:Ignorable="d">
|
||||
mc:Ignorable="d"
|
||||
PreviewMouseMove="ResultList_MouseMove"
|
||||
PreviewMouseLeftButtonDown="ResultList_PreviewMouseLeftButtonDown"
|
||||
PreviewMouseLeftButtonUp="ResultListBox_OnPreviewMouseUp"
|
||||
PreviewMouseRightButtonDown="ResultListBox_OnPreviewMouseRightButtonDown">
|
||||
<!-- IsSynchronizedWithCurrentItem: http://stackoverflow.com/a/7833798/2833083 -->
|
||||
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button HorizontalAlignment="Stretch">
|
||||
<Button
|
||||
HorizontalAlignment="Stretch">
|
||||
<Button.Template>
|
||||
<ControlTemplate>
|
||||
<ContentPresenter Content="{TemplateBinding Button.Content}" />
|
||||
|
|
@ -84,7 +89,7 @@
|
|||
x:Name="ImageIcon"
|
||||
Width="{Binding IconXY}"
|
||||
Height="{Binding IconXY}"
|
||||
Margin="0,0,0,0"
|
||||
Margin="0,0,0,0" IsHitTestVisible="False"
|
||||
HorizontalAlignment="Center"
|
||||
Source="{Binding Image, TargetNullValue={x:Null}}"
|
||||
Stretch="Uniform"
|
||||
|
|
@ -136,7 +141,9 @@
|
|||
DockPanel.Dock="Left"
|
||||
Style="{DynamicResource ItemTitleStyle}"
|
||||
Text="{Binding Result.Title}"
|
||||
ToolTip="{Binding ShowTitleToolTip}">
|
||||
ToolTip="{Binding ShowTitleToolTip}"
|
||||
ToolTipService.ShowOnDisabled="True"
|
||||
IsEnabled="False">
|
||||
<vm:ResultsViewModel.FormattedText>
|
||||
<MultiBinding Converter="{StaticResource HighlightTextConverter}">
|
||||
<Binding Path="Result.Title" />
|
||||
|
|
@ -147,10 +154,11 @@
|
|||
<TextBlock
|
||||
x:Name="SubTitle"
|
||||
Grid.Row="1"
|
||||
IsEnabled="False"
|
||||
ToolTipService.ShowOnDisabled="True"
|
||||
Style="{DynamicResource ItemSubTitleStyle}"
|
||||
Text="{Binding Result.SubTitle}"
|
||||
ToolTip="{Binding ShowSubTitleToolTip}" />
|
||||
|
||||
ToolTip="{Binding ShowSubTitleToolTip}"/>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
using System.Windows;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using Flow.Launcher.ViewModel;
|
||||
|
||||
namespace Flow.Launcher
|
||||
{
|
||||
|
|
@ -14,6 +17,36 @@ namespace Flow.Launcher
|
|||
InitializeComponent();
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty RightClickResultCommandProperty =
|
||||
DependencyProperty.Register("RightClickResultCommand", typeof(ICommand), typeof(ResultListBox), new UIPropertyMetadata(null));
|
||||
|
||||
public ICommand RightClickResultCommand
|
||||
{
|
||||
get
|
||||
{
|
||||
return (ICommand)GetValue(RightClickResultCommandProperty);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetValue(RightClickResultCommandProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty LeftClickResultCommandProperty =
|
||||
DependencyProperty.Register("LeftClickResultCommand", typeof(ICommand), typeof(ResultListBox), new UIPropertyMetadata(null));
|
||||
|
||||
public ICommand LeftClickResultCommand
|
||||
{
|
||||
get
|
||||
{
|
||||
return (ICommand)GetValue(LeftClickResultCommandProperty);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetValue(LeftClickResultCommandProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (e.AddedItems.Count > 0 && e.AddedItems[0] != null)
|
||||
|
|
@ -24,7 +57,7 @@ namespace Flow.Launcher
|
|||
|
||||
private void OnMouseEnter(object sender, MouseEventArgs e)
|
||||
{
|
||||
lock(_lock)
|
||||
lock (_lock)
|
||||
{
|
||||
curItem = (ListBoxItem)sender;
|
||||
var p = e.GetPosition((IInputElement)sender);
|
||||
|
|
@ -34,7 +67,7 @@ namespace Flow.Launcher
|
|||
|
||||
private void OnMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
lock(_lock)
|
||||
lock (_lock)
|
||||
{
|
||||
var p = e.GetPosition((IInputElement)sender);
|
||||
if (_lastpos != p)
|
||||
|
|
@ -46,7 +79,7 @@ namespace Flow.Launcher
|
|||
|
||||
private void ListBox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
lock(_lock)
|
||||
lock (_lock)
|
||||
{
|
||||
if (curItem != null)
|
||||
{
|
||||
|
|
@ -54,5 +87,65 @@ namespace Flow.Launcher
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Point start;
|
||||
private string path;
|
||||
private string query;
|
||||
|
||||
private void ResultList_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (Mouse.DirectlyOver is not FrameworkElement { DataContext: ResultViewModel result })
|
||||
return;
|
||||
|
||||
path = result.Result.CopyText;
|
||||
query = result.Result.OriginQuery.RawQuery;
|
||||
start = e.GetPosition(null);
|
||||
}
|
||||
|
||||
private void ResultList_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.LeftButton != MouseButtonState.Pressed)
|
||||
{
|
||||
start = default;
|
||||
path = string.Empty;
|
||||
query = string.Empty;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!File.Exists(path) && !Directory.Exists(path))
|
||||
return;
|
||||
|
||||
Point mousePosition = e.GetPosition(null);
|
||||
Vector diff = this.start - mousePosition;
|
||||
|
||||
if (Math.Abs(diff.X) < SystemParameters.MinimumHorizontalDragDistance
|
||||
|| Math.Abs(diff.Y) < SystemParameters.MinimumVerticalDragDistance)
|
||||
return;
|
||||
|
||||
var data = new DataObject(DataFormats.FileDrop, new[]
|
||||
{
|
||||
path
|
||||
});
|
||||
DragDrop.DoDragDrop((DependencyObject)sender, data, DragDropEffects.Move | DragDropEffects.Copy);
|
||||
|
||||
App.API.ChangeQuery(query, true);
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
private void ResultListBox_OnPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (Mouse.DirectlyOver is not FrameworkElement { DataContext: ResultViewModel result })
|
||||
return;
|
||||
|
||||
RightClickResultCommand?.Execute(result.Result);
|
||||
}
|
||||
private void ResultListBox_OnPreviewMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (Mouse.DirectlyOver is not FrameworkElement { DataContext: ResultViewModel result })
|
||||
return;
|
||||
|
||||
LeftClickResultCommand?.Execute(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -8,14 +8,19 @@ using Flow.Launcher.Plugin;
|
|||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using Flow.Launcher.ViewModel;
|
||||
using ModernWpf;
|
||||
using ModernWpf.Controls;
|
||||
using System;
|
||||
using System.Drawing.Printing;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Navigation;
|
||||
using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window;
|
||||
using Button = System.Windows.Controls.Button;
|
||||
using Control = System.Windows.Controls.Control;
|
||||
using KeyEventArgs = System.Windows.Input.KeyEventArgs;
|
||||
|
|
@ -39,10 +44,6 @@ namespace Flow.Launcher
|
|||
API = api;
|
||||
InitializePosition();
|
||||
InitializeComponent();
|
||||
|
||||
CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(StoreListBox.ItemsSource);
|
||||
PropertyGroupDescription groupDescription = new PropertyGroupDescription("Category");
|
||||
view.GroupDescriptions.Add(groupDescription);
|
||||
}
|
||||
|
||||
#region General
|
||||
|
|
@ -154,7 +155,7 @@ namespace Flow.Launcher
|
|||
}
|
||||
}
|
||||
|
||||
private void OnnEditCustomHotkeyClick(object sender, RoutedEventArgs e)
|
||||
private void OnEditCustomHotkeyClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var item = viewModel.SelectedCustomPluginHotkey;
|
||||
if (item != null)
|
||||
|
|
@ -252,6 +253,7 @@ namespace Flow.Launcher
|
|||
|
||||
private void OnClosed(object sender, EventArgs e)
|
||||
{
|
||||
settings.SettingWindowState = WindowState;
|
||||
settings.SettingWindowTop = Top;
|
||||
settings.SettingWindowLeft = Left;
|
||||
viewModel.Save();
|
||||
|
|
@ -296,17 +298,35 @@ namespace Flow.Launcher
|
|||
}
|
||||
}
|
||||
|
||||
private void OnPluginStoreRefreshClick(object sender, RoutedEventArgs e)
|
||||
private static T FindParent<T>(DependencyObject child) where T : DependencyObject
|
||||
{
|
||||
_ = viewModel.RefreshExternalPluginsAsync();
|
||||
}
|
||||
//get parent item
|
||||
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
|
||||
|
||||
//we've reached the end of the tree
|
||||
if (parentObject == null) return null;
|
||||
|
||||
//check if the parent matches the type we're looking for
|
||||
T parent = parentObject as T;
|
||||
if (parent != null)
|
||||
return parent;
|
||||
else
|
||||
return FindParent<T>(parentObject);
|
||||
}
|
||||
|
||||
private void OnExternalPluginInstallClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is Button { DataContext: PluginStoreItemViewModel plugin })
|
||||
if (sender is not Button { DataContext: PluginStoreItemViewModel plugin } button)
|
||||
{
|
||||
viewModel.DisplayPluginQuery($"install {plugin.Name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (storeClickedButton != null)
|
||||
{
|
||||
FlyoutService.GetFlyout(storeClickedButton).Hide();
|
||||
}
|
||||
|
||||
viewModel.DisplayPluginQuery($"install {plugin.Name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7"));
|
||||
}
|
||||
|
||||
private void OnExternalPluginUninstallClick(object sender, MouseButtonEventArgs e)
|
||||
|
|
@ -317,18 +337,30 @@ namespace Flow.Launcher
|
|||
viewModel.DisplayPluginQuery($"uninstall {name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7"));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void OnExternalPluginUninstallClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (storeClickedButton != null)
|
||||
{
|
||||
FlyoutService.GetFlyout(storeClickedButton).Hide();
|
||||
}
|
||||
|
||||
if (sender is Button { DataContext: PluginStoreItemViewModel plugin })
|
||||
viewModel.DisplayPluginQuery($"uninstall {plugin.Name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7"));
|
||||
|
||||
}
|
||||
|
||||
private void OnExternalPluginUpdateClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (storeClickedButton != null)
|
||||
{
|
||||
FlyoutService.GetFlyout(storeClickedButton).Hide();
|
||||
}
|
||||
if (sender is Button { DataContext: PluginStoreItemViewModel plugin })
|
||||
viewModel.DisplayPluginQuery($"update {plugin.Name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7"));
|
||||
|
||||
}
|
||||
|
||||
private void window_MouseDown(object sender, MouseButtonEventArgs e) /* for close hotkey popup */
|
||||
|
|
@ -381,11 +413,34 @@ namespace Flow.Launcher
|
|||
restoreButton.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_StateChanged(object sender, EventArgs e)
|
||||
{
|
||||
RefreshMaximizeRestoreButton();
|
||||
}
|
||||
|
||||
#region Shortcut
|
||||
|
||||
private void OnDeleteCustomShortCutClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
viewModel.DeleteSelectedCustomShortcut();
|
||||
}
|
||||
|
||||
private void OnEditCustomShortCutClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (viewModel.EditSelectedCustomShortcut())
|
||||
{
|
||||
customShortcutView.Items.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAddCustomShortCutClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
viewModel.AddCustomShortcut();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private CollectionView pluginListView;
|
||||
private CollectionView pluginStoreView;
|
||||
|
||||
|
|
@ -463,6 +518,7 @@ namespace Flow.Launcher
|
|||
{
|
||||
ClockDisplay();
|
||||
}
|
||||
|
||||
public void ClockDisplay()
|
||||
{
|
||||
if (settings.UseClock)
|
||||
|
|
@ -498,6 +554,7 @@ namespace Flow.Launcher
|
|||
Top = WindowTop();
|
||||
Left = WindowLeft();
|
||||
}
|
||||
WindowState = settings.SettingWindowState;
|
||||
}
|
||||
public double WindowLeft()
|
||||
{
|
||||
|
|
@ -517,5 +574,21 @@ namespace Flow.Launcher
|
|||
return top;
|
||||
}
|
||||
|
||||
private Button storeClickedButton;
|
||||
|
||||
private void StoreListItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is not Button button)
|
||||
return;
|
||||
|
||||
storeClickedButton = button;
|
||||
|
||||
var flyout = FlyoutService.GetFlyout(button);
|
||||
flyout.Closed += (_, _) =>
|
||||
{
|
||||
storeClickedButton = null;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ using Flow.Launcher.Plugin.SharedCommands;
|
|||
using Flow.Launcher.Storage;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
using System.Text;
|
||||
using System.Threading.Channels;
|
||||
using ISavable = Flow.Launcher.Plugin.ISavable;
|
||||
using System.IO;
|
||||
|
|
@ -76,12 +77,21 @@ namespace Flow.Launcher.ViewModel
|
|||
_userSelectedRecord = _userSelectedRecordStorage.Load();
|
||||
_topMostRecord = _topMostRecordStorage.Load();
|
||||
|
||||
ContextMenu = new ResultsViewModel(Settings);
|
||||
Results = new ResultsViewModel(Settings);
|
||||
History = new ResultsViewModel(Settings);
|
||||
|
||||
ContextMenu = new ResultsViewModel(Settings)
|
||||
{
|
||||
LeftClickResultCommand = OpenResultCommand, RightClickResultCommand = LoadContextMenuCommand
|
||||
};
|
||||
Results = new ResultsViewModel(Settings)
|
||||
{
|
||||
LeftClickResultCommand = OpenResultCommand, RightClickResultCommand = LoadContextMenuCommand
|
||||
};
|
||||
History = new ResultsViewModel(Settings)
|
||||
{
|
||||
LeftClickResultCommand = OpenResultCommand, RightClickResultCommand = LoadContextMenuCommand
|
||||
};
|
||||
_selectedResults = Results;
|
||||
|
||||
InitializeKeyCommands();
|
||||
|
||||
RegisterViewUpdate();
|
||||
RegisterResultsUpdatedEvent();
|
||||
|
|
@ -156,165 +166,164 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void InitializeKeyCommands()
|
||||
[RelayCommand]
|
||||
private async Task ReloadPluginDataAsync()
|
||||
{
|
||||
EscCommand = new RelayCommand(_ =>
|
||||
Hide();
|
||||
|
||||
await PluginManager.ReloadDataAsync().ConfigureAwait(false);
|
||||
Notification.Show(InternationalizationManager.Instance.GetTranslation("success"), InternationalizationManager.Instance.GetTranslation("completedSuccessfully"));
|
||||
}
|
||||
[RelayCommand]
|
||||
private void LoadHistory()
|
||||
{
|
||||
if (SelectedIsFromQueryResults())
|
||||
{
|
||||
if (!SelectedIsFromQueryResults())
|
||||
{
|
||||
SelectedResults = Results;
|
||||
}
|
||||
else
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
});
|
||||
|
||||
ClearQueryCommand = new RelayCommand(_ =>
|
||||
SelectedResults = History;
|
||||
History.SelectedIndex = _history.Items.Count - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrEmpty(QueryText))
|
||||
{
|
||||
ChangeQueryText(string.Empty);
|
||||
|
||||
// Push Event to UI SystemQuery has changed
|
||||
//OnPropertyChanged(nameof(SystemQueryText));
|
||||
}
|
||||
});
|
||||
|
||||
SelectNextItemCommand = new RelayCommand(_ => { SelectedResults.SelectNextResult(); });
|
||||
|
||||
SelectPrevItemCommand = new RelayCommand(_ => { SelectedResults.SelectPrevResult(); });
|
||||
|
||||
SelectNextPageCommand = new RelayCommand(_ => { SelectedResults.SelectNextPage(); });
|
||||
|
||||
SelectPrevPageCommand = new RelayCommand(_ => { SelectedResults.SelectPrevPage(); });
|
||||
|
||||
SelectFirstResultCommand = new RelayCommand(_ => SelectedResults.SelectFirstResult());
|
||||
|
||||
StartHelpCommand = new RelayCommand(_ =>
|
||||
SelectedResults = Results;
|
||||
}
|
||||
}
|
||||
[RelayCommand]
|
||||
private void LoadContextMenu()
|
||||
{
|
||||
if (SelectedIsFromQueryResults())
|
||||
{
|
||||
PluginManager.API.OpenUrl("https://github.com/Flow-Launcher/Flow.Launcher/wiki/Flow-Launcher/");
|
||||
});
|
||||
OpenSettingCommand = new RelayCommand(_ => { App.API.OpenSettingDialog(); });
|
||||
OpenResultCommand = new RelayCommand(async index =>
|
||||
// When switch to ContextMenu from QueryResults, but no item being chosen, should do nothing
|
||||
// i.e. Shift+Enter/Ctrl+O right after Alt + Space should do nothing
|
||||
if (SelectedResults.SelectedItem != null)
|
||||
SelectedResults = ContextMenu;
|
||||
}
|
||||
else
|
||||
{
|
||||
var results = SelectedResults;
|
||||
SelectedResults = Results;
|
||||
}
|
||||
}
|
||||
[RelayCommand]
|
||||
private void Backspace(object index)
|
||||
{
|
||||
var query = QueryBuilder.Build(QueryText.Trim(), PluginManager.NonGlobalPlugins);
|
||||
|
||||
if (index != null)
|
||||
// GetPreviousExistingDirectory does not require trailing '\', otherwise will return empty string
|
||||
var path = FilesFolders.GetPreviousExistingDirectory((_) => true, query.Search.TrimEnd('\\'));
|
||||
|
||||
var actionKeyword = string.IsNullOrEmpty(query.ActionKeyword) ? string.Empty : $"{query.ActionKeyword} ";
|
||||
|
||||
ChangeQueryText($"{actionKeyword}{path}");
|
||||
}
|
||||
[RelayCommand]
|
||||
private void AutocompleteQuery()
|
||||
{
|
||||
var result = SelectedResults.SelectedItem?.Result;
|
||||
if (result != null && SelectedIsFromQueryResults()) // SelectedItem returns null if selection is empty.
|
||||
{
|
||||
var autoCompleteText = result.Title;
|
||||
|
||||
if (!string.IsNullOrEmpty(result.AutoCompleteText))
|
||||
{
|
||||
results.SelectedIndex = int.Parse(index.ToString()!);
|
||||
autoCompleteText = result.AutoCompleteText;
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(SelectedResults.SelectedItem?.QuerySuggestionText))
|
||||
{
|
||||
autoCompleteText = SelectedResults.SelectedItem.QuerySuggestionText;
|
||||
}
|
||||
|
||||
var result = results.SelectedItem?.Result;
|
||||
if (result == null)
|
||||
var specialKeyState = GlobalHotkey.CheckModifiers();
|
||||
if (specialKeyState.ShiftPressed)
|
||||
{
|
||||
return;
|
||||
autoCompleteText = result.SubTitle;
|
||||
}
|
||||
var hideWindow = await result.ExecuteAsync(new ActionContext
|
||||
|
||||
ChangeQueryText(autoCompleteText);
|
||||
}
|
||||
}
|
||||
[RelayCommand]
|
||||
private async Task OpenResultAsync(string index)
|
||||
{
|
||||
var results = SelectedResults;
|
||||
if (index is not null)
|
||||
{
|
||||
results.SelectedIndex = int.Parse(index);
|
||||
}
|
||||
var result = results.SelectedItem?.Result;
|
||||
if (result == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var hideWindow = await result.ExecuteAsync(new ActionContext
|
||||
{
|
||||
SpecialKeyState = GlobalHotkey.CheckModifiers()
|
||||
}).ConfigureAwait(false);
|
||||
})
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (hideWindow)
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
|
||||
if (SelectedIsFromQueryResults())
|
||||
{
|
||||
_userSelectedRecord.Add(result);
|
||||
_history.Add(result.OriginQuery.RawQuery);
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedResults = Results;
|
||||
}
|
||||
});
|
||||
|
||||
AutocompleteQueryCommand = new RelayCommand(_ =>
|
||||
{
|
||||
var result = SelectedResults.SelectedItem?.Result;
|
||||
if (result != null && SelectedIsFromQueryResults()) // SelectedItem returns null if selection is empty.
|
||||
{
|
||||
var autoCompleteText = result.Title;
|
||||
|
||||
if (!string.IsNullOrEmpty(result.AutoCompleteText))
|
||||
{
|
||||
autoCompleteText = result.AutoCompleteText;
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(SelectedResults.SelectedItem?.QuerySuggestionText))
|
||||
{
|
||||
autoCompleteText = SelectedResults.SelectedItem.QuerySuggestionText;
|
||||
}
|
||||
|
||||
var specialKeyState = GlobalHotkey.CheckModifiers();
|
||||
if (specialKeyState.ShiftPressed)
|
||||
{
|
||||
autoCompleteText = result.SubTitle;
|
||||
}
|
||||
|
||||
ChangeQueryText(autoCompleteText);
|
||||
}
|
||||
});
|
||||
|
||||
BackspaceCommand = new RelayCommand(index =>
|
||||
{
|
||||
var query = QueryBuilder.Build(QueryText.Trim(), PluginManager.NonGlobalPlugins);
|
||||
|
||||
// GetPreviousExistingDirectory does not require trailing '\', otherwise will return empty string
|
||||
var path = FilesFolders.GetPreviousExistingDirectory((_) => true, query.Search.TrimEnd('\\'));
|
||||
|
||||
var actionKeyword = string.IsNullOrEmpty(query.ActionKeyword) ? string.Empty : $"{query.ActionKeyword} ";
|
||||
|
||||
ChangeQueryText($"{actionKeyword}{path}");
|
||||
});
|
||||
|
||||
LoadContextMenuCommand = new RelayCommand(_ =>
|
||||
{
|
||||
if (SelectedIsFromQueryResults())
|
||||
{
|
||||
// When switch to ContextMenu from QueryResults, but no item being chosen, should do nothing
|
||||
// i.e. Shift+Enter/Ctrl+O right after Alt + Space should do nothing
|
||||
if (SelectedResults.SelectedItem != null)
|
||||
SelectedResults = ContextMenu;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedResults = Results;
|
||||
}
|
||||
});
|
||||
|
||||
LoadHistoryCommand = new RelayCommand(_ =>
|
||||
{
|
||||
if (SelectedIsFromQueryResults())
|
||||
{
|
||||
SelectedResults = History;
|
||||
History.SelectedIndex = _history.Items.Count - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedResults = Results;
|
||||
}
|
||||
});
|
||||
|
||||
ReloadPluginDataCommand = new RelayCommand(_ =>
|
||||
if (hideWindow)
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
|
||||
_ = PluginManager
|
||||
.ReloadDataAsync()
|
||||
.ContinueWith(_ =>
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
Notification.Show(
|
||||
InternationalizationManager.Instance.GetTranslation("success"),
|
||||
InternationalizationManager.Instance.GetTranslation("completedSuccessfully")
|
||||
);
|
||||
}), TaskScheduler.Default)
|
||||
.ConfigureAwait(false);
|
||||
});
|
||||
if (SelectedIsFromQueryResults())
|
||||
{
|
||||
_userSelectedRecord.Add(result);
|
||||
_history.Add(result.OriginQuery.RawQuery);
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedResults = Results;
|
||||
}
|
||||
}
|
||||
[RelayCommand]
|
||||
private void OpenSetting()
|
||||
{
|
||||
App.API.OpenSettingDialog();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SelectHelp()
|
||||
{
|
||||
PluginManager.API.OpenUrl("https://www.flowlauncher.com/docs/#/usage-tips");
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SelectFirstResult()
|
||||
{
|
||||
SelectedResults.SelectFirstResult();
|
||||
}
|
||||
[RelayCommand]
|
||||
private void SelectPrevPage()
|
||||
{
|
||||
SelectedResults.SelectPrevPage();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SelectNextPage()
|
||||
{
|
||||
SelectedResults.SelectNextPage();
|
||||
}
|
||||
[RelayCommand]
|
||||
private void SelectPrevItem()
|
||||
{
|
||||
SelectedResults.SelectPrevResult();
|
||||
}
|
||||
[RelayCommand]
|
||||
private void SelectNextItem()
|
||||
{
|
||||
SelectedResults.SelectNextResult();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Esc()
|
||||
{
|
||||
if (!SelectedIsFromQueryResults())
|
||||
{
|
||||
SelectedResults = Results;
|
||||
}
|
||||
else
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
@ -362,9 +371,9 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
if (MainWindowWidth + 100 > 1920 || Settings.WindowSize == 1920)
|
||||
{
|
||||
Settings.WindowSize = 1920;
|
||||
Settings.WindowSize = 1920;
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
Settings.WindowSize += 100;
|
||||
Settings.WindowLeft -= 50;
|
||||
|
|
@ -410,6 +419,7 @@ namespace Flow.Launcher.ViewModel
|
|||
/// but we don't want to move cursor to end when query is updated from TextBox
|
||||
/// </summary>
|
||||
/// <param name="queryText"></param>
|
||||
/// <param name="reQuery">Force query even when Query Text doesn't change</param>
|
||||
public void ChangeQueryText(string queryText, bool reQuery = false)
|
||||
{
|
||||
if (QueryText != queryText)
|
||||
|
|
@ -488,25 +498,6 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
public string PluginIconPath { get; set; } = null;
|
||||
|
||||
public ICommand EscCommand { get; set; }
|
||||
public ICommand BackspaceCommand { get; set; }
|
||||
public ICommand SelectNextItemCommand { get; set; }
|
||||
public ICommand SelectPrevItemCommand { get; set; }
|
||||
public ICommand SelectNextPageCommand { get; set; }
|
||||
public ICommand SelectPrevPageCommand { get; set; }
|
||||
public ICommand SelectFirstResultCommand { get; set; }
|
||||
public ICommand StartHelpCommand { get; set; }
|
||||
public ICommand LoadContextMenuCommand { get; set; }
|
||||
public ICommand LoadHistoryCommand { get; set; }
|
||||
public ICommand OpenResultCommand { get; set; }
|
||||
public ICommand OpenSettingCommand { get; set; }
|
||||
public ICommand ReloadPluginDataCommand { get; set; }
|
||||
public ICommand ClearQueryCommand { get; private set; }
|
||||
|
||||
public ICommand CopyToClipboard { get; set; }
|
||||
|
||||
public ICommand AutocompleteQueryCommand { get; set; }
|
||||
|
||||
public string OpenResultCommandModifiers { get; private set; }
|
||||
|
||||
public string Image => Constant.QueryTextBoxIconImagePath;
|
||||
|
|
@ -621,7 +612,9 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
_updateSource?.Cancel();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(QueryText))
|
||||
var query = ConstructQuery(QueryText, Settings.CustomShortcuts, Settings.BuiltinShortcuts);
|
||||
|
||||
if (query == null) // shortcut expanded
|
||||
{
|
||||
Results.Clear();
|
||||
Results.Visbility = Visibility.Collapsed;
|
||||
|
|
@ -646,7 +639,6 @@ namespace Flow.Launcher.ViewModel
|
|||
if (currentCancellationToken.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
var query = QueryBuilder.Build(QueryText, PluginManager.NonGlobalPlugins);
|
||||
|
||||
// handle the exclusiveness of plugin using action keyword
|
||||
RemoveOldQueryResults(query);
|
||||
|
|
@ -736,6 +728,40 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
private Query ConstructQuery(string queryText, IEnumerable<CustomShortcutModel> customShortcuts, IEnumerable<BuiltinShortcutModel> builtInShortcuts)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(queryText))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuilder queryBuilder = new(queryText);
|
||||
StringBuilder queryBuilderTmp = new(queryText);
|
||||
|
||||
foreach (var shortcut in customShortcuts)
|
||||
{
|
||||
if (queryBuilder.Equals(shortcut.Key))
|
||||
{
|
||||
queryBuilder.Replace(shortcut.Key, shortcut.Expand());
|
||||
}
|
||||
|
||||
queryBuilder.Replace('@' + shortcut.Key, shortcut.Expand());
|
||||
}
|
||||
|
||||
foreach (var shortcut in builtInShortcuts)
|
||||
{
|
||||
queryBuilder.Replace(shortcut.Key, shortcut.Expand());
|
||||
queryBuilderTmp.Replace(shortcut.Key, shortcut.Expand());
|
||||
}
|
||||
|
||||
// show expanded builtin shortcuts
|
||||
// use private field to avoid infinite recursion
|
||||
_queryText = queryBuilderTmp.ToString();
|
||||
|
||||
var query = QueryBuilder.Build(queryBuilder.ToString().Trim(), PluginManager.NonGlobalPlugins);
|
||||
return query;
|
||||
}
|
||||
|
||||
private void RemoveOldQueryResults(Query query)
|
||||
{
|
||||
if (_lastQuery?.ActionKeyword != query?.ActionKeyword)
|
||||
|
|
@ -966,28 +992,26 @@ namespace Flow.Launcher.ViewModel
|
|||
var result = Results.SelectedItem?.Result;
|
||||
if (result != null)
|
||||
{
|
||||
string copyText = string.IsNullOrEmpty(result.CopyText) ? result.SubTitle : result.CopyText;
|
||||
string copyText = result.CopyText;
|
||||
var isFile = File.Exists(copyText);
|
||||
var isFolder = Directory.Exists(copyText);
|
||||
if (isFile || isFolder)
|
||||
{
|
||||
var paths = new StringCollection();
|
||||
paths.Add(copyText);
|
||||
var paths = new StringCollection
|
||||
{
|
||||
copyText
|
||||
};
|
||||
|
||||
Clipboard.SetFileDropList(paths);
|
||||
App.API.ShowMsg(
|
||||
App.API.GetTranslation("copy")
|
||||
+ " "
|
||||
+ (isFile ? App.API.GetTranslation("fileTitle") : App.API.GetTranslation("folderTitle")),
|
||||
$"{App.API.GetTranslation("copy")} {(isFile ? App.API.GetTranslation("fileTitle") : App.API.GetTranslation("folderTitle"))}",
|
||||
App.API.GetTranslation("completedSuccessfully"));
|
||||
}
|
||||
else
|
||||
{
|
||||
Clipboard.SetDataObject(copyText.ToString());
|
||||
Clipboard.SetDataObject(copyText);
|
||||
App.API.ShowMsg(
|
||||
App.API.GetTranslation("copy")
|
||||
+ " "
|
||||
+ App.API.GetTranslation("textTitle"),
|
||||
$"{App.API.GetTranslation("copy")} {App.API.GetTranslation("textTitle")}",
|
||||
App.API.GetTranslation("completedSuccessfully"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ using System.Windows;
|
|||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Flow.Launcher.ViewModel
|
||||
{
|
||||
|
|
@ -49,6 +51,9 @@ namespace Flow.Launcher.ViewModel
|
|||
public ResultViewModel SelectedItem { get; set; }
|
||||
public Thickness Margin { get; set; }
|
||||
public Visibility Visbility { get; set; } = Visibility.Collapsed;
|
||||
|
||||
public ICommand RightClickResultCommand { get; init; }
|
||||
public ICommand LeftClickResultCommand { get; init; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
|
|
|||
|
|
@ -19,10 +19,12 @@ using Flow.Launcher.Infrastructure.Storage;
|
|||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedModels;
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
namespace Flow.Launcher.ViewModel
|
||||
{
|
||||
public class SettingWindowViewModel : BaseModel
|
||||
public partial class SettingWindowViewModel : BaseModel
|
||||
{
|
||||
private readonly Updater _updater;
|
||||
private readonly IPortable _portable;
|
||||
|
|
@ -212,11 +214,19 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
public List<string> OpenResultModifiersList => new List<string> { KeyConstant.Alt, KeyConstant.Ctrl, $"{KeyConstant.Ctrl}+{KeyConstant.Alt}" };
|
||||
public List<string> OpenResultModifiersList => new List<string>
|
||||
{
|
||||
KeyConstant.Alt,
|
||||
KeyConstant.Ctrl,
|
||||
$"{KeyConstant.Ctrl}+{KeyConstant.Alt}"
|
||||
};
|
||||
private Internationalization _translater => InternationalizationManager.Instance;
|
||||
public List<Language> Languages => _translater.LoadAvailableLanguages();
|
||||
public IEnumerable<int> MaxResultsRange => Enumerable.Range(2, 16);
|
||||
|
||||
public ObservableCollection<CustomShortcutModel> CustomShortcuts => Settings.CustomShortcuts;
|
||||
public ObservableCollection<BuiltinShortcutModel> BuiltinShortcuts => Settings.BuiltinShortcuts;
|
||||
|
||||
public string TestProxy()
|
||||
{
|
||||
var proxyServer = Settings.Proxy.Server;
|
||||
|
|
@ -275,7 +285,10 @@ namespace Flow.Launcher.ViewModel
|
|||
var metadatas = PluginManager.AllPlugins
|
||||
.OrderBy(x => x.Metadata.Disabled)
|
||||
.ThenBy(y => y.Metadata.Name)
|
||||
.Select(p => new PluginViewModel { PluginPair = p })
|
||||
.Select(p => new PluginViewModel
|
||||
{
|
||||
PluginPair = p
|
||||
})
|
||||
.ToList();
|
||||
return metadatas;
|
||||
}
|
||||
|
|
@ -318,7 +331,8 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
public async Task RefreshExternalPluginsAsync()
|
||||
[RelayCommand]
|
||||
private async Task RefreshExternalPluginsAsync()
|
||||
{
|
||||
await PluginsManifest.UpdateManifestAsync();
|
||||
OnPropertyChanged(nameof(ExternalPlugins));
|
||||
|
|
@ -397,7 +411,11 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
var key = $"ColorScheme{e}";
|
||||
var display = _translater.GetTranslation(key);
|
||||
var m = new ColorScheme { Display = display, Value = e, };
|
||||
var m = new ColorScheme
|
||||
{
|
||||
Display = display,
|
||||
Value = e,
|
||||
};
|
||||
modes.Add(m);
|
||||
}
|
||||
return modes;
|
||||
|
|
@ -519,8 +537,13 @@ namespace Flow.Launcher.ViewModel
|
|||
var bitmap = new BitmapImage();
|
||||
bitmap.BeginInit();
|
||||
bitmap.StreamSource = memStream;
|
||||
bitmap.DecodePixelWidth = 800;
|
||||
bitmap.DecodePixelHeight = 600;
|
||||
bitmap.EndInit();
|
||||
var brush = new ImageBrush(bitmap) { Stretch = Stretch.UniformToFill };
|
||||
var brush = new ImageBrush(bitmap)
|
||||
{
|
||||
Stretch = Stretch.UniformToFill
|
||||
};
|
||||
return brush;
|
||||
}
|
||||
else
|
||||
|
|
@ -548,19 +571,19 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
Title = "WebSearch",
|
||||
SubTitle = "Search the web with different search engine support",
|
||||
IcoPath =Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.WebSearch\Images\web_search.png")
|
||||
IcoPath = Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.WebSearch\Images\web_search.png")
|
||||
},
|
||||
new Result
|
||||
{
|
||||
Title = "Program",
|
||||
SubTitle = "Launch programs as admin or a different user",
|
||||
IcoPath =Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.Program\Images\program.png")
|
||||
IcoPath = Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.Program\Images\program.png")
|
||||
},
|
||||
new Result
|
||||
{
|
||||
Title = "ProcessKiller",
|
||||
SubTitle = "Terminate unwanted processes",
|
||||
IcoPath =Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.ProcessKiller\Images\app.png")
|
||||
IcoPath = Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.ProcessKiller\Images\app.png")
|
||||
}
|
||||
};
|
||||
var vm = new ResultsViewModel(Settings);
|
||||
|
|
@ -574,8 +597,8 @@ namespace Flow.Launcher.ViewModel
|
|||
get
|
||||
{
|
||||
if (Fonts.SystemFontFamilies.Count(o =>
|
||||
o.FamilyNames.Values != null &&
|
||||
o.FamilyNames.Values.Contains(Settings.QueryBoxFont)) > 0)
|
||||
o.FamilyNames.Values != null &&
|
||||
o.FamilyNames.Values.Contains(Settings.QueryBoxFont)) > 0)
|
||||
{
|
||||
var font = new FontFamily(Settings.QueryBoxFont);
|
||||
return font;
|
||||
|
|
@ -602,7 +625,7 @@ namespace Flow.Launcher.ViewModel
|
|||
Settings.QueryBoxFontStyle,
|
||||
Settings.QueryBoxFontWeight,
|
||||
Settings.QueryBoxFontStretch
|
||||
));
|
||||
));
|
||||
return typeface;
|
||||
}
|
||||
set
|
||||
|
|
@ -619,8 +642,8 @@ namespace Flow.Launcher.ViewModel
|
|||
get
|
||||
{
|
||||
if (Fonts.SystemFontFamilies.Count(o =>
|
||||
o.FamilyNames.Values != null &&
|
||||
o.FamilyNames.Values.Contains(Settings.ResultFont)) > 0)
|
||||
o.FamilyNames.Values != null &&
|
||||
o.FamilyNames.Values.Contains(Settings.ResultFont)) > 0)
|
||||
{
|
||||
var font = new FontFamily(Settings.ResultFont);
|
||||
return font;
|
||||
|
|
@ -647,7 +670,7 @@ namespace Flow.Launcher.ViewModel
|
|||
Settings.ResultFontStyle,
|
||||
Settings.ResultFontWeight,
|
||||
Settings.ResultFontStretch
|
||||
));
|
||||
));
|
||||
return typeface;
|
||||
}
|
||||
set
|
||||
|
|
@ -669,6 +692,65 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
#endregion
|
||||
|
||||
#region shortcut
|
||||
|
||||
public CustomShortcutModel? SelectedCustomShortcut { get; set; }
|
||||
|
||||
public void DeleteSelectedCustomShortcut()
|
||||
{
|
||||
var item = SelectedCustomShortcut;
|
||||
if (item == null)
|
||||
{
|
||||
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
|
||||
return;
|
||||
}
|
||||
|
||||
string deleteWarning = string.Format(
|
||||
InternationalizationManager.Instance.GetTranslation("deleteCustomShortcutWarning"),
|
||||
item?.Key, item?.Value);
|
||||
if (MessageBox.Show(deleteWarning, InternationalizationManager.Instance.GetTranslation("delete"),
|
||||
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
||||
{
|
||||
Settings.CustomShortcuts.Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
public bool EditSelectedCustomShortcut()
|
||||
{
|
||||
var item = SelectedCustomShortcut;
|
||||
if (item == null)
|
||||
{
|
||||
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
|
||||
return false;
|
||||
}
|
||||
|
||||
var shortcutSettingWindow = new CustomShortcutSetting(item.Key, item.Value, this);
|
||||
if (shortcutSettingWindow.ShowDialog() == true)
|
||||
{
|
||||
item.Key = shortcutSettingWindow.Key;
|
||||
item.Value = shortcutSettingWindow.Value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void AddCustomShortcut()
|
||||
{
|
||||
var shortcutSettingWindow = new CustomShortcutSetting(this);
|
||||
if (shortcutSettingWindow.ShowDialog() == true)
|
||||
{
|
||||
var shortcut = new CustomShortcutModel(shortcutSettingWindow.Key, shortcutSettingWindow.Value);
|
||||
Settings.CustomShortcuts.Add(shortcut);
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShortcutExists(string key)
|
||||
{
|
||||
return Settings.CustomShortcuts.Any(x => x.Key == key) || Settings.BuiltinShortcuts.Any(x => x.Key == key);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region about
|
||||
|
||||
public string Website => Constant.Website;
|
||||
|
|
@ -676,7 +758,20 @@ namespace Flow.Launcher.ViewModel
|
|||
public string Documentation => Constant.Documentation;
|
||||
public string Docs => Constant.Docs;
|
||||
public string Github => Constant.GitHub;
|
||||
public static string Version => Constant.Version;
|
||||
public string Version
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Constant.Version == "1.0.0")
|
||||
{
|
||||
return Constant.Dev;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Constant.Version;
|
||||
}
|
||||
}
|
||||
}
|
||||
public string ActivatedTimes => string.Format(_translater.GetTranslation("about_activate_times"), Settings.ActivateTimes);
|
||||
|
||||
public string CheckLogFolder
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Flow.Launcher.Plugin.BrowserBookmark.Models;
|
||||
using Flow.Launcher.Plugin.BrowserBookmark.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SQLite;
|
||||
|
|
@ -130,4 +130,4 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ProjectGuid>{9B130CC5-14FB-41FF-B310-0A95B6894C37}</ProjectGuid>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Flow.Launcher.Plugin.BrowserBookmark</RootNamespace>
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
|
|
@ -39,15 +39,6 @@
|
|||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="x64\SQLite.Interop.dll">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="x86\SQLite.Interop.dll">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj" />
|
||||
|
|
@ -64,8 +55,8 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Data.SQLite" Version="1.0.114.4" />
|
||||
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.114.3" />
|
||||
<PackageReference Include="System.Data.SQLite" Version="1.0.116" />
|
||||
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.116" />
|
||||
<PackageReference Include="UnidecodeSharp" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
@ -73,4 +64,19 @@
|
|||
<Compile Remove="Bookmark.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="CopyDLLs" AfterTargets="Build">
|
||||
<Message Text="Executing CopyDLLs task" Importance="High" />
|
||||
<Copy
|
||||
SourceFiles="$(TargetDir)\runtimes\win-x64\native\SQLite.Interop.dll"
|
||||
DestinationFolder="$(TargetDir)\x64" />
|
||||
<Copy
|
||||
SourceFiles="$(TargetDir)\runtimes\win-x86\native\SQLite.Interop.dll"
|
||||
DestinationFolder="$(TargetDir)\x86" />
|
||||
</Target>
|
||||
|
||||
<Target Name="DeleteRuntimesFolder" AfterTargets="CopyDLLs">
|
||||
<Message Text="Deleting runtimes folder" Importance="High" />
|
||||
<RemoveDir Directories="$(TargetDir)\runtimes" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -6,12 +6,13 @@
|
|||
xmlns:local="clr-namespace:Flow.Launcher.Plugin.Explorer.Views"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Title="{DynamicResource plugin_explorer_manageactionkeywords_header}"
|
||||
Width="400"
|
||||
SizeToContent="Height"
|
||||
Width="Auto"
|
||||
Height="255"
|
||||
Background="{DynamicResource PopuBGColor}"
|
||||
DataContext="{Binding RelativeSource={RelativeSource Self}}"
|
||||
Foreground="{DynamicResource PopupTextColor}"
|
||||
ResizeMode="NoResize"
|
||||
SizeToContent="Width"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
mc:Ignorable="d">
|
||||
<WindowChrome.WindowChrome>
|
||||
|
|
@ -68,7 +69,7 @@
|
|||
|
||||
<StackPanel Margin="0,10,0,0" Orientation="Horizontal">
|
||||
<TextBlock
|
||||
Width="150"
|
||||
MinWidth="150"
|
||||
Margin="0,10,15,10"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
|
|
@ -78,18 +79,19 @@
|
|||
Name="TxtCurrentActionKeyword"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Width="105"
|
||||
Width="135"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
PreviewKeyDown="TxtCurrentActionKeyword_OnKeyDown"
|
||||
Text="{Binding ActionKeyword}" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0,10,0,15" Orientation="Horizontal">
|
||||
<TextBlock Margin="0 0 15 0 "
|
||||
<TextBlock
|
||||
MinWidth="150"
|
||||
Margin="0,0,18,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Width="150"
|
||||
Text="{DynamicResource plugin_explorer_actionkeyword_enabled}" />
|
||||
<CheckBox
|
||||
Name="ChkActionKeywordEnabled"
|
||||
|
|
|
|||
|
|
@ -5,12 +5,13 @@
|
|||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Title="{DynamicResource flowlauncher_plugin_program_directory}"
|
||||
Width="400"
|
||||
Width="Auto"
|
||||
Height="276"
|
||||
Background="{DynamicResource PopuBGColor}"
|
||||
Foreground="{DynamicResource PopupTextColor}"
|
||||
SizeToContent="Height"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
mc:Ignorable="d">
|
||||
ResizeMode="NoResize"
|
||||
SizeToContent="Width"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
<WindowChrome.WindowChrome>
|
||||
<WindowChrome CaptionHeight="32" ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
|
||||
</WindowChrome.WindowChrome>
|
||||
|
|
@ -19,7 +20,6 @@
|
|||
<RowDefinition />
|
||||
<RowDefinition Height="80" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Grid.Row="0">
|
||||
<StackPanel>
|
||||
<Grid>
|
||||
|
|
@ -53,33 +53,80 @@
|
|||
</Button>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="26,12,26,0">
|
||||
<StackPanel Margin="0,0,0,12">
|
||||
<StackPanel Margin="26,0,26,0">
|
||||
<StackPanel Grid.Row="0" Margin="0,0,0,12">
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Margin="0,0,0,0"
|
||||
FontSize="20"
|
||||
FontWeight="SemiBold"
|
||||
Text="{DynamicResource flowlauncher_plugin_program_directory}"
|
||||
Text="{DynamicResource flowlauncher_plugin_program_edit_program_source_title}"
|
||||
TextAlignment="Left" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
|
||||
<TextBox
|
||||
Name="Directory"
|
||||
Width="268"
|
||||
Margin="0,7"
|
||||
VerticalAlignment="Center" />
|
||||
<Button
|
||||
Width="70"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Right"
|
||||
Click="BrowseButton_Click"
|
||||
Content="{DynamicResource flowlauncher_plugin_program_browse}" />
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
FontSize="14"
|
||||
Text="{DynamicResource flowlauncher_plugin_program_edit_program_source_tips}"
|
||||
TextAlignment="Left"
|
||||
TextWrapping="WrapWithOverflow" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Margin="0,10,10,0" Orientation="Horizontal">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Text="{DynamicResource flowlauncher_plugin_program_directory}" />
|
||||
<DockPanel
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
LastChildFill="True">
|
||||
<Button
|
||||
MinWidth="70"
|
||||
HorizontalAlignment="Stretch"
|
||||
Click="BrowseButton_Click"
|
||||
Content="{DynamicResource flowlauncher_plugin_program_browse}"
|
||||
DockPanel.Dock="Right" />
|
||||
<TextBox
|
||||
Name="Directory"
|
||||
MinWidth="300"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center" />
|
||||
</DockPanel>
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Text="{DynamicResource flowlauncher_plugin_program_enabled}" />
|
||||
<CheckBox
|
||||
x:Name="Chkbox"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="10,0"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Margin="0,14,0,0"
|
||||
Background="{DynamicResource PopupButtonAreaBGColor}"
|
||||
BorderBrush="{DynamicResource PopupButtonAreaBorderColor}"
|
||||
BorderThickness="0,1,0,0">
|
||||
|
|
@ -87,19 +134,17 @@
|
|||
<Button
|
||||
x:Name="btnCancel"
|
||||
MinWidth="140"
|
||||
Margin="0,0,5,0"
|
||||
Margin="10,0,5,0"
|
||||
Click="BtnCancel_OnClick"
|
||||
Content="{DynamicResource cancel}" />
|
||||
|
||||
<Button
|
||||
x:Name="btnAdd"
|
||||
MinWidth="140"
|
||||
Margin="5,0,0,0"
|
||||
HorizontalAlignment="Right"
|
||||
Click="ButtonAdd_OnClick"
|
||||
Margin="5,0,10,0"
|
||||
Click="BtnAdd_OnClick"
|
||||
Content="{DynamicResource flowlauncher_plugin_program_update}"
|
||||
Style="{DynamicResource AccentButtonStyle}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
</Window>
|
||||
</Window>
|
||||
|
|
@ -9,11 +9,12 @@ namespace Flow.Launcher.Plugin.Program
|
|||
/// <summary>
|
||||
/// Interaction logic for AddProgramSource.xaml
|
||||
/// </summary>
|
||||
public partial class AddProgramSource
|
||||
public partial class AddProgramSource : Window
|
||||
{
|
||||
private PluginInitContext _context;
|
||||
private Settings.ProgramSource _editing;
|
||||
private ProgramSource _editing;
|
||||
private Settings _settings;
|
||||
private bool update;
|
||||
|
||||
public AddProgramSource(PluginInitContext context, Settings settings)
|
||||
{
|
||||
|
|
@ -21,14 +22,19 @@ namespace Flow.Launcher.Plugin.Program
|
|||
_context = context;
|
||||
_settings = settings;
|
||||
Directory.Focus();
|
||||
Chkbox.IsChecked = true;
|
||||
update = false;
|
||||
btnAdd.Content = _context.API.GetTranslation("flowlauncher_plugin_program_add");
|
||||
}
|
||||
|
||||
public AddProgramSource(Settings.ProgramSource edit, Settings settings)
|
||||
public AddProgramSource(PluginInitContext context, Settings settings, ProgramSource source)
|
||||
{
|
||||
_editing = edit;
|
||||
_settings = settings;
|
||||
|
||||
InitializeComponent();
|
||||
_context = context;
|
||||
_editing = source;
|
||||
_settings = settings;
|
||||
update = true;
|
||||
Chkbox.IsChecked = _editing.Enabled;
|
||||
Directory.Text = _editing.Location;
|
||||
}
|
||||
|
||||
|
|
@ -47,34 +53,54 @@ namespace Flow.Launcher.Plugin.Program
|
|||
Close();
|
||||
}
|
||||
|
||||
private void ButtonAdd_OnClick(object sender, RoutedEventArgs e)
|
||||
private void BtnAdd_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
string s = Directory.Text;
|
||||
if (!System.IO.Directory.Exists(s))
|
||||
string path = Directory.Text;
|
||||
bool modified = false;
|
||||
if (!System.IO.Directory.Exists(path))
|
||||
{
|
||||
System.Windows.MessageBox.Show(_context.API.GetTranslation("flowlauncher_plugin_program_invalid_path"));
|
||||
return;
|
||||
}
|
||||
if (_editing == null)
|
||||
if (!update)
|
||||
{
|
||||
if (!ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == Directory.Text))
|
||||
if (!ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier.Equals(path, System.StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
var source = new ProgramSource
|
||||
{
|
||||
Location = Directory.Text,
|
||||
UniqueIdentifier = Directory.Text
|
||||
};
|
||||
|
||||
var source = new ProgramSource(path);
|
||||
modified = true;
|
||||
_settings.ProgramSources.Insert(0, source);
|
||||
ProgramSetting.ProgramSettingDisplayList.Add(source);
|
||||
}
|
||||
else
|
||||
{
|
||||
System.Windows.MessageBox.Show(_context.API.GetTranslation("flowlauncher_plugin_program_duplicate_program_source"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_editing.Location = Directory.Text;
|
||||
// Separate checks to avoid changing UniqueIdentifier of UWP
|
||||
if (!_editing.Location.Equals(path, System.StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (ProgramSetting.ProgramSettingDisplayList
|
||||
.Any(x => x.UniqueIdentifier.Equals(path, System.StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
// Check if the new location is used
|
||||
// No need to check win32 or uwp, just override them
|
||||
System.Windows.MessageBox.Show(_context.API.GetTranslation("flowlauncher_plugin_program_duplicate_program_source"));
|
||||
return;
|
||||
}
|
||||
modified = true;
|
||||
_editing.Location = path; // Changes UniqueIdentifier internally
|
||||
}
|
||||
if (_editing.Enabled != Chkbox.IsChecked)
|
||||
{
|
||||
modified = true;
|
||||
_editing.Enabled = Chkbox.IsChecked ?? true;
|
||||
}
|
||||
}
|
||||
|
||||
DialogResult = true;
|
||||
DialogResult = modified;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,58 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Plugin.Program.Programs;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Program
|
||||
{
|
||||
//internal static class FileChangeWatcher
|
||||
//{
|
||||
// private static readonly List<string> WatchedPath = new List<string>();
|
||||
// // todo remove previous watcher events
|
||||
// public static void AddAll(List<UnregisteredPrograms> sources, string[] suffixes)
|
||||
// {
|
||||
// foreach (var s in sources)
|
||||
// {
|
||||
// if (Directory.Exists(s.Location))
|
||||
// {
|
||||
// AddWatch(s.Location, suffixes);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// public static void AddWatch(string path, string[] programSuffixes, bool includingSubDirectory = true)
|
||||
// {
|
||||
// if (WatchedPath.Contains(path)) return;
|
||||
// if (!Directory.Exists(path))
|
||||
// {
|
||||
// Log.Warn($"|FileChangeWatcher|{path} doesn't exist");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// WatchedPath.Add(path);
|
||||
// foreach (string fileType in programSuffixes)
|
||||
// {
|
||||
// FileSystemWatcher watcher = new FileSystemWatcher
|
||||
// {
|
||||
// Path = path,
|
||||
// IncludeSubdirectories = includingSubDirectory,
|
||||
// Filter = $"*.{fileType}",
|
||||
// EnableRaisingEvents = true
|
||||
// };
|
||||
// watcher.Changed += FileChanged;
|
||||
// watcher.Created += FileChanged;
|
||||
// watcher.Deleted += FileChanged;
|
||||
// watcher.Renamed += FileChanged;
|
||||
// }
|
||||
// }
|
||||
|
||||
// private static void FileChanged(object source, FileSystemEventArgs e)
|
||||
// {
|
||||
// Task.Run(() =>
|
||||
// {
|
||||
// Main.IndexPrograms();
|
||||
// });
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
|
@ -10,16 +10,21 @@
|
|||
<system:String x:Key="flowlauncher_plugin_program_add">Add</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_name">Name</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_enable">Enable</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_enabled">Enabled</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_disable">Disable</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_location">Location</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_all_programs">All Programs</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_suffixes">File Type</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_reindex">Reindex</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_indexing">Indexing</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_index_start">Index Start Menu</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_index_source">Index Sources</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_index_option">Options</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_index_start">Start Menu</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_index_start_tooltip">When enabled, Flow will load programs from the start menu</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_index_registry">Index Registry</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_index_registry">Registry</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_index_registry_tooltip">When enabled, Flow will load programs from the registry</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_index_PATH">PATH</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_index_PATH_tooltip">When enabled, Flow will load programs from the PATH environment variable</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_enable_hidelnkpath">Hide app path</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_enable_hidelnkpath_tooltip">For executable files such as UWP or lnk, hide the file path from being visible</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_enable_description">Search in Program Description</system:String>
|
||||
|
|
@ -34,8 +39,12 @@
|
|||
|
||||
<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_duplicate_program_source">Another program source with the same location alreaday exists.</system:String>
|
||||
|
||||
<system:String x:Key="flowlauncher_plugin_program_update">OK</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">Program Source</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_tips">Edit directory and status of this program source.</system:String>
|
||||
|
||||
<system:String x:Key="flowlauncher_plugin_program_update">Update</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_only_index_tip">Program Plugin will only index files with selected suffixes and .url files with selected protocols.</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_update_file_suffixes">Successfully updated file suffixes</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_suffixes_cannot_empty">File suffixes can't be empty</system:String>
|
||||
|
|
@ -52,7 +61,7 @@
|
|||
Insert file suffixes you want to index. Suffixes should be separated by ';'. (ex>bat;py)
|
||||
</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_protocol_tooltip">
|
||||
Insert protocols of .url files you want to index. Protocols should be separated by ';'. (ex>ftp;netflix)
|
||||
Insert protocols of .url files you want to index. Protocols should be separated by ';', and should end with "://". (ex>ftp://;mailto://)
|
||||
</system:String>
|
||||
|
||||
<system:String x:Key="flowlauncher_plugin_program_run_as_different_user">Run As Different User</system:String>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,8 @@
|
|||
using NLog;
|
||||
using NLog.Config;
|
||||
using NLog.Targets;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Program.Logger
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
|
@ -11,9 +9,8 @@ using Flow.Launcher.Infrastructure.Logger;
|
|||
using Flow.Launcher.Infrastructure.Storage;
|
||||
using Flow.Launcher.Plugin.Program.Programs;
|
||||
using Flow.Launcher.Plugin.Program.Views;
|
||||
using Flow.Launcher.Plugin.Program.Views.Models;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Program
|
||||
|
|
@ -82,13 +79,9 @@ namespace Flow.Launcher.Plugin.Program
|
|||
Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Preload programs cost", () =>
|
||||
{
|
||||
_win32Storage = new BinaryStorage<Win32[]>("Win32");
|
||||
_win32s = _win32Storage.TryLoad(new Win32[]
|
||||
{
|
||||
});
|
||||
_win32s = _win32Storage.TryLoad(Array.Empty<Win32>());
|
||||
_uwpStorage = new BinaryStorage<UWP.Application[]>("UWP");
|
||||
_uwps = _uwpStorage.TryLoad(new UWP.Application[]
|
||||
{
|
||||
});
|
||||
_uwps = _uwpStorage.TryLoad(Array.Empty<UWP.Application>());
|
||||
});
|
||||
Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload win32 programs <{_win32s.Length}>");
|
||||
Log.Info($"|Flow.Launcher.Plugin.Program.Main|Number of preload uwps <{_uwps.Length}>");
|
||||
|
|
@ -102,14 +95,14 @@ namespace Flow.Launcher.Plugin.Program
|
|||
|
||||
var b = Task.Run(() =>
|
||||
{
|
||||
Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexUwpPrograms);
|
||||
Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|UWPPRogram index cost", IndexUwpPrograms);
|
||||
});
|
||||
|
||||
if (cacheEmpty)
|
||||
await Task.WhenAll(a, b);
|
||||
|
||||
Win32.WatchProgramUpdate(_settings);
|
||||
UWP.WatchPackageChange();
|
||||
_ = UWP.WatchPackageChange();
|
||||
}
|
||||
|
||||
public static void IndexWin32Programs()
|
||||
|
|
@ -123,18 +116,23 @@ namespace Flow.Launcher.Plugin.Program
|
|||
{
|
||||
var windows10 = new Version(10, 0);
|
||||
var support = Environment.OSVersion.Version.Major >= windows10.Major;
|
||||
var applications = support ? UWP.All() : new UWP.Application[]
|
||||
{
|
||||
};
|
||||
var applications = support ? UWP.All() : Array.Empty<UWP.Application>();
|
||||
_uwps = applications;
|
||||
ResetCache();
|
||||
}
|
||||
|
||||
public static async Task IndexProgramsAsync()
|
||||
{
|
||||
var t1 = Task.Run(IndexWin32Programs);
|
||||
var t2 = Task.Run(IndexUwpPrograms);
|
||||
await Task.WhenAll(t1, t2).ConfigureAwait(false);
|
||||
var a = Task.Run(() =>
|
||||
{
|
||||
Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|Win32Program index cost", IndexWin32Programs);
|
||||
});
|
||||
|
||||
var b = Task.Run(() =>
|
||||
{
|
||||
Stopwatch.Normal("|Flow.Launcher.Plugin.Program.Main|UWPProgram index cost", IndexUwpPrograms);
|
||||
});
|
||||
await Task.WhenAll(a, b).ConfigureAwait(false);
|
||||
_settings.LastIndexTime = DateTime.Today;
|
||||
}
|
||||
|
||||
|
|
@ -190,29 +188,33 @@ namespace Flow.Launcher.Plugin.Program
|
|||
return menuOptions;
|
||||
}
|
||||
|
||||
private void DisableProgram(IProgram programToDelete)
|
||||
private static void DisableProgram(IProgram programToDelete)
|
||||
{
|
||||
if (_settings.DisabledProgramSources.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
|
||||
return;
|
||||
|
||||
if (_uwps.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
|
||||
_uwps.FirstOrDefault(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)
|
||||
.Enabled = false;
|
||||
|
||||
if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
|
||||
_win32s.FirstOrDefault(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)
|
||||
.Enabled = false;
|
||||
|
||||
_settings.DisabledProgramSources
|
||||
.Add(
|
||||
new Settings.DisabledProgramSource
|
||||
{
|
||||
Name = programToDelete.Name,
|
||||
Location = programToDelete.Location,
|
||||
UniqueIdentifier = programToDelete.UniqueIdentifier,
|
||||
Enabled = false
|
||||
}
|
||||
);
|
||||
{
|
||||
var program = _uwps.First(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier);
|
||||
program.Enabled = false;
|
||||
_settings.DisabledProgramSources.Add(new ProgramSource(program));
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
IndexUwpPrograms();
|
||||
_settings.LastIndexTime = DateTime.Today;
|
||||
});
|
||||
}
|
||||
else if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier))
|
||||
{
|
||||
var program = _win32s.First(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier);
|
||||
program.Enabled = false;
|
||||
_settings.DisabledProgramSources.Add(new ProgramSource(program));
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
IndexWin32Programs();
|
||||
_settings.LastIndexTime = DateTime.Today;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void StartProcess(Func<ProcessStartInfo, Process> runProcess, ProcessStartInfo info)
|
||||
|
|
@ -233,6 +235,7 @@ namespace Flow.Launcher.Plugin.Program
|
|||
{
|
||||
await IndexProgramsAsync();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Win32.Dispose();
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@
|
|||
Title="{DynamicResource flowlauncher_plugin_program_suffixes}"
|
||||
Width="600"
|
||||
Background="{DynamicResource PopuBGColor}"
|
||||
Foreground="{DynamicResource PopupTextColor}"
|
||||
DataContext="{Binding RelativeSource={RelativeSource Self}}"
|
||||
Foreground="{DynamicResource PopupTextColor}"
|
||||
ResizeMode="NoResize"
|
||||
SizeToContent="Height"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
|
|
@ -163,17 +163,32 @@
|
|||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Text="{DynamicResource flowlauncher_plugin_program_suffixes_excutable_types}" />
|
||||
<CheckBox Name="apprefMS" Margin="10,0,0,0" IsChecked="{Binding SuffixesStatus[appref-ms]}">appref-ms</CheckBox>
|
||||
<CheckBox Name="exe" Margin="10,0,0,0" IsChecked="{Binding SuffixesStatus[exe]}">exe</CheckBox>
|
||||
<CheckBox Name="lnk" Margin="10,0,0,0" IsChecked="{Binding SuffixesStatus[lnk]}">lnk</CheckBox>
|
||||
<CheckBox
|
||||
Name="apprefMS"
|
||||
Margin="10,0,0,0"
|
||||
IsChecked="{Binding SuffixesStatus[appref-ms]}">
|
||||
appref-ms
|
||||
</CheckBox>
|
||||
<CheckBox
|
||||
Name="exe"
|
||||
Margin="10,0,0,0"
|
||||
IsChecked="{Binding SuffixesStatus[exe]}">
|
||||
exe
|
||||
</CheckBox>
|
||||
<CheckBox
|
||||
Name="lnk"
|
||||
Margin="10,0,0,0"
|
||||
IsChecked="{Binding SuffixesStatus[lnk]}">
|
||||
lnk
|
||||
</CheckBox>
|
||||
<CheckBox
|
||||
Name="CustomFiles"
|
||||
Margin="10,0,0,0"
|
||||
IsChecked="{Binding UseCustomSuffixes}"
|
||||
Content="{DynamicResource flowlauncher_plugin_program_suffixes_custom_file_types}" />
|
||||
Content="{DynamicResource flowlauncher_plugin_program_suffixes_custom_file_types}"
|
||||
IsChecked="{Binding UseCustomSuffixes}" />
|
||||
<TextBox
|
||||
x:Name="tbSuffixes"
|
||||
Margin="10,4,0,6"
|
||||
Margin="10,4,0,10"
|
||||
Style="{StaticResource CustomFileTypeTextBox}" />
|
||||
</StackPanel>
|
||||
|
||||
|
|
@ -189,17 +204,29 @@
|
|||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Text="{DynamicResource flowlauncher_plugin_program_suffixes_URL_types}" />
|
||||
<CheckBox Name="steam" Margin="10,0,0,0" IsChecked="{Binding ProtocolsStatus[steam]}" Content="{DynamicResource flowlauncher_plugin_program_suffixes_URL_steam}"></CheckBox>
|
||||
<CheckBox Name="epic" Margin="10,0,0,0" IsChecked="{Binding ProtocolsStatus[epic]}" Content="{DynamicResource flowlauncher_plugin_program_suffixes_URL_epic}"></CheckBox>
|
||||
<CheckBox Name="http" Margin="10,0,0,0" IsChecked="{Binding ProtocolsStatus[http]}" Content="{DynamicResource flowlauncher_plugin_program_suffixes_URL_http}"></CheckBox>
|
||||
<CheckBox
|
||||
Name="steam"
|
||||
Margin="10,0,0,0"
|
||||
Content="{DynamicResource flowlauncher_plugin_program_suffixes_URL_steam}"
|
||||
IsChecked="{Binding ProtocolsStatus[steam]}" />
|
||||
<CheckBox
|
||||
Name="epic"
|
||||
Margin="10,0,0,0"
|
||||
Content="{DynamicResource flowlauncher_plugin_program_suffixes_URL_epic}"
|
||||
IsChecked="{Binding ProtocolsStatus[epic]}" />
|
||||
<CheckBox
|
||||
Name="http"
|
||||
Margin="10,0,0,0"
|
||||
Content="{DynamicResource flowlauncher_plugin_program_suffixes_URL_http}"
|
||||
IsChecked="{Binding ProtocolsStatus[http]}" />
|
||||
<CheckBox
|
||||
Name="CustomProtocol"
|
||||
Margin="10,0,0,0"
|
||||
IsChecked="{Binding UseCustomProtocols}"
|
||||
Content="{DynamicResource flowlauncher_plugin_program_suffixes_custom_urls}" />
|
||||
Content="{DynamicResource flowlauncher_plugin_program_suffixes_custom_urls}"
|
||||
IsChecked="{Binding UseCustomProtocols}" />
|
||||
<TextBox
|
||||
x:Name="tbProtocols"
|
||||
Margin="10,4,0,6"
|
||||
Margin="10,4,0,0"
|
||||
Style="{StaticResource CustomURLTypeTextBox}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Program.Programs
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,20 +1,18 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Program.Programs
|
||||
{
|
||||
public class AppxPackageHelper
|
||||
{
|
||||
// This function returns a list of attributes of applications
|
||||
public List<IAppxManifestApplication> getAppsFromManifest(IStream stream)
|
||||
public static List<IAppxManifestApplication> GetAppsFromManifest(IStream stream)
|
||||
{
|
||||
IAppxFactory appxFactory = (IAppxFactory)new AppxFactory();
|
||||
List<IAppxManifestApplication> apps = new List<IAppxManifestApplication>();
|
||||
var appxFactory = new AppxFactory();
|
||||
var reader = ((IAppxFactory)appxFactory).CreateManifestReader(stream);
|
||||
var reader = appxFactory.CreateManifestReader(stream);
|
||||
var manifestApps = reader.GetApplications();
|
||||
while (manifestApps.GetHasCurrent())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.IO;
|
||||
using Accessibility;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using System.Security.Policy;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Program.Programs
|
||||
{
|
||||
|
|
@ -100,8 +97,8 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
}
|
||||
|
||||
// To initialize the app description
|
||||
public String description = String.Empty;
|
||||
|
||||
public string description = string.Empty;
|
||||
public string arguments = string.Empty;
|
||||
|
||||
// Retrieve the target path using Shell Link
|
||||
public string retrieveTargetPath(string path)
|
||||
|
|
@ -125,13 +122,16 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
buffer = new StringBuilder(MAX_PATH);
|
||||
((IShellLinkW)link).GetDescription(buffer, MAX_PATH);
|
||||
description = buffer.ToString();
|
||||
|
||||
buffer.Clear();
|
||||
((IShellLinkW)link).GetArguments(buffer, MAX_PATH);
|
||||
arguments = buffer.ToString();
|
||||
}
|
||||
|
||||
// To release unmanaged memory
|
||||
Marshal.ReleaseComObject(link);
|
||||
|
||||
return target;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ using Flow.Launcher.Plugin.Program.Logger;
|
|||
using Rect = System.Windows.Rect;
|
||||
using Flow.Launcher.Plugin.SharedModels;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading.Channels;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Program.Programs
|
||||
|
|
@ -42,39 +41,27 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
FullName = package.Id.FullName;
|
||||
FamilyName = package.Id.FamilyName;
|
||||
InitializeAppInfo();
|
||||
Apps = Apps.Where(a =>
|
||||
{
|
||||
var valid =
|
||||
!string.IsNullOrEmpty(a.UserModelId) &&
|
||||
!string.IsNullOrEmpty(a.DisplayName);
|
||||
return valid;
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
private void InitializeAppInfo()
|
||||
{
|
||||
AppxPackageHelper _helper = new AppxPackageHelper();
|
||||
var path = Path.Combine(Location, "AppxManifest.xml");
|
||||
|
||||
var namespaces = XmlNamespaces(path);
|
||||
InitPackageVersion(namespaces);
|
||||
|
||||
const uint noAttribute = 0x80;
|
||||
const Stgm exclusiveRead = Stgm.Read | Stgm.ShareExclusive;
|
||||
var hResult = SHCreateStreamOnFileEx(path, exclusiveRead, noAttribute, false, null, out IStream stream);
|
||||
const Stgm nonExclusiveRead = Stgm.Read | Stgm.ShareDenyNone;
|
||||
var hResult = SHCreateStreamOnFileEx(path, nonExclusiveRead, noAttribute, false, null, out IStream stream);
|
||||
|
||||
if (hResult == Hresult.Ok)
|
||||
{
|
||||
var apps = new List<Application>();
|
||||
List<AppxPackageHelper.IAppxManifestApplication> _apps = AppxPackageHelper.GetAppsFromManifest(stream);
|
||||
|
||||
List<AppxPackageHelper.IAppxManifestApplication> _apps = _helper.getAppsFromManifest(stream);
|
||||
foreach (var _app in _apps)
|
||||
{
|
||||
var app = new Application(_app, this);
|
||||
apps.Add(app);
|
||||
}
|
||||
|
||||
Apps = apps.Where(a => a.AppListEntry != "none").ToArray();
|
||||
Apps = _apps.Select(x => new Application(x, this))
|
||||
.Where(a => !string.IsNullOrEmpty(a.UserModelId)
|
||||
&& !string.IsNullOrEmpty(a.DisplayName))
|
||||
.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -82,17 +69,17 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
ProgramLogger.LogException($"|UWP|InitializeAppInfo|{path}" +
|
||||
"|Error caused while trying to get the details of the UWP program", e);
|
||||
|
||||
Apps = new List<Application>().ToArray();
|
||||
Apps = Array.Empty<Application>();
|
||||
}
|
||||
|
||||
if (Marshal.ReleaseComObject(stream) > 0)
|
||||
if (stream != null && Marshal.ReleaseComObject(stream) > 0)
|
||||
{
|
||||
Log.Error("Flow.Launcher.Plugin.Program.Programs.UWP", "AppxManifest.xml was leaked");
|
||||
}
|
||||
}
|
||||
|
||||
/// http://www.hanselman.com/blog/GetNamespacesFromAnXMLDocumentWithXPathDocumentAndLINQToXML.aspx
|
||||
private string[] XmlNamespaces(string path)
|
||||
private static string[] XmlNamespaces(string path)
|
||||
{
|
||||
XDocument z = XDocument.Load(path);
|
||||
if (z.Root != null)
|
||||
|
|
@ -110,9 +97,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
ProgramLogger.LogException($"|UWP|XmlNamespaces|{path}" +
|
||||
$"|Error occured while trying to get the XML from {path}", new ArgumentNullException());
|
||||
|
||||
return new string[]
|
||||
{
|
||||
};
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -178,16 +163,13 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
|
||||
var updatedListWithoutDisabledApps = applications
|
||||
.Where(t1 => !Main._settings.DisabledProgramSources
|
||||
.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
|
||||
.Select(x => x);
|
||||
.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier));
|
||||
|
||||
return updatedListWithoutDisabledApps.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Application[]
|
||||
{
|
||||
};
|
||||
return Array.Empty<Application>();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -233,9 +215,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
}
|
||||
else
|
||||
{
|
||||
return new Package[]
|
||||
{
|
||||
};
|
||||
return Array.Empty<Package>();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -297,8 +277,8 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
[Serializable]
|
||||
public class Application : IProgram
|
||||
{
|
||||
public string AppListEntry { get; set; }
|
||||
public string UniqueIdentifier { get; set; }
|
||||
private string _uid = string.Empty;
|
||||
public string UniqueIdentifier { get => _uid; set => _uid = value == null ? string.Empty : value.ToLowerInvariant(); }
|
||||
public string DisplayName { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string UserModelId { get; set; }
|
||||
|
|
@ -317,7 +297,6 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
|
||||
public Application() { }
|
||||
|
||||
|
||||
public Result Result(string query, IPublicAPI api)
|
||||
{
|
||||
string title;
|
||||
|
|
@ -544,7 +523,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
}
|
||||
}
|
||||
|
||||
public string FormattedPriReferenceValue(string packageName, string rawPriReferenceValue)
|
||||
public static string FormattedPriReferenceValue(string packageName, string rawPriReferenceValue)
|
||||
{
|
||||
const string prefix = "ms-resource:";
|
||||
|
||||
|
|
@ -601,92 +580,97 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
// windows 8.1 https://msdn.microsoft.com/en-us/library/windows/apps/hh965372.aspx#target_size
|
||||
// windows 8 https://msdn.microsoft.com/en-us/library/windows/apps/br211475.aspx
|
||||
|
||||
string path;
|
||||
if (uri.Contains("\\"))
|
||||
{
|
||||
path = Path.Combine(Package.Location, uri);
|
||||
}
|
||||
else
|
||||
string path = Path.Combine(Package.Location, uri);
|
||||
|
||||
var logoPath = TryToFindLogo(uri, path);
|
||||
if (String.IsNullOrEmpty(logoPath))
|
||||
{
|
||||
// TODO: Don't know why, just keep it at the moment
|
||||
// Maybe on older version of Windows 10?
|
||||
// for C:\Windows\MiracastView etc
|
||||
path = Path.Combine(Package.Location, "Assets", uri);
|
||||
return TryToFindLogo(uri, Path.Combine(Package.Location, "Assets", uri));
|
||||
}
|
||||
return logoPath;
|
||||
|
||||
var extension = Path.GetExtension(path);
|
||||
if (extension != null)
|
||||
string TryToFindLogo(string uri, string path)
|
||||
{
|
||||
var end = path.Length - extension.Length;
|
||||
var prefix = path.Substring(0, end);
|
||||
var paths = new List<string>
|
||||
var extension = Path.GetExtension(path);
|
||||
if (extension != null)
|
||||
{
|
||||
path
|
||||
};
|
||||
//if (File.Exists(path))
|
||||
//{
|
||||
// return path; // shortcut, avoid enumerating files
|
||||
//}
|
||||
|
||||
var scaleFactors = new Dictionary<PackageVersion, List<int>>
|
||||
{
|
||||
// scale factors on win10: https://docs.microsoft.com/en-us/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets#asset-size-tables,
|
||||
var logoNamePrefix = Path.GetFileNameWithoutExtension(uri); // e.g Square44x44
|
||||
var logoDir = Path.GetDirectoryName(path); // e.g ..\..\Assets
|
||||
if (String.IsNullOrEmpty(logoNamePrefix) || String.IsNullOrEmpty(logoDir) || !Directory.Exists(logoDir))
|
||||
{
|
||||
PackageVersion.Windows10, new List<int>
|
||||
{
|
||||
100,
|
||||
125,
|
||||
150,
|
||||
200,
|
||||
400
|
||||
}
|
||||
},
|
||||
// Known issue: Edge always triggers it since logo is not at uri
|
||||
ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Package.Location}" +
|
||||
$"|{UserModelId} can't find logo uri for {uri} in package location (logo name or directory not found): {Package.Location}", new FileNotFoundException());
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var files = Directory.EnumerateFiles(logoDir);
|
||||
|
||||
// Currently we don't care which one to choose
|
||||
// Just ignore all qualifiers
|
||||
// select like logo.[xxx_yyy].png
|
||||
// https://learn.microsoft.com/en-us/windows/uwp/app-resources/tailor-resources-lang-scale-contrast
|
||||
var logos = files.Where(file =>
|
||||
Path.GetFileName(file)?.StartsWith(logoNamePrefix, StringComparison.OrdinalIgnoreCase) ?? false
|
||||
&& extension.Equals(Path.GetExtension(file), StringComparison.OrdinalIgnoreCase)
|
||||
);
|
||||
|
||||
var selected = logos.FirstOrDefault();
|
||||
var closest = selected;
|
||||
int min = int.MaxValue;
|
||||
foreach(var logo in logos)
|
||||
{
|
||||
PackageVersion.Windows81, new List<int>
|
||||
|
||||
var imageStream = File.OpenRead(logo);
|
||||
var decoder = BitmapDecoder.Create(imageStream, BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.None);
|
||||
var height = decoder.Frames[0].PixelHeight;
|
||||
var width = decoder.Frames[0].PixelWidth;
|
||||
int pixelCountDiff = Math.Abs(height * width - 1936); // 44*44=1936
|
||||
if(pixelCountDiff < min)
|
||||
{
|
||||
100,
|
||||
120,
|
||||
140,
|
||||
160,
|
||||
180
|
||||
}
|
||||
},
|
||||
{
|
||||
PackageVersion.Windows8, new List<int>
|
||||
{
|
||||
100
|
||||
// try to find the closest to 44x44 logo
|
||||
closest = logo;
|
||||
if (pixelCountDiff == 0)
|
||||
break; // found 44x44
|
||||
min = pixelCountDiff;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (scaleFactors.ContainsKey(Package.Version))
|
||||
{
|
||||
foreach (var factor in scaleFactors[Package.Version])
|
||||
selected = closest;
|
||||
if (!string.IsNullOrEmpty(selected))
|
||||
{
|
||||
paths.Add($"{prefix}.scale-{factor}{extension}");
|
||||
return selected;
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Package.Location}" +
|
||||
$"|{UserModelId} can't find logo uri for {uri} in package location (can't find specified logo): {Package.Location}", new FileNotFoundException());
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
var selected = paths.FirstOrDefault(File.Exists);
|
||||
if (!string.IsNullOrEmpty(selected))
|
||||
{
|
||||
return selected;
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Package.Location}" +
|
||||
$"|{UserModelId} can't find logo uri for {uri} in package location: {Package.Location}", new FileNotFoundException());
|
||||
$"|Unable to find extension from {uri} for {UserModelId} " +
|
||||
$"in package location {Package.Location}", new FileNotFoundException());
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Package.Location}" +
|
||||
$"|Unable to find extension from {uri} for {UserModelId} " +
|
||||
$"in package location {Package.Location}", new FileNotFoundException());
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public ImageSource Logo()
|
||||
{
|
||||
var logo = ImageFromPath(LogoPath);
|
||||
var plated = PlatedImage(logo);
|
||||
var plated = PlatedImage(logo); // TODO: maybe get plated directly from app package?
|
||||
|
||||
// todo magic! temp fix for cross thread object
|
||||
plated.Freeze();
|
||||
|
|
@ -696,9 +680,15 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
|
||||
private BitmapImage ImageFromPath(string path)
|
||||
{
|
||||
// TODO: Consider using infrastructure.image.imageloader?
|
||||
if (File.Exists(path))
|
||||
{
|
||||
var image = new BitmapImage(new Uri(path));
|
||||
var image = new BitmapImage();
|
||||
image.BeginInit();
|
||||
image.UriSource = new Uri(path);
|
||||
image.CacheOption = BitmapCacheOption.OnLoad;
|
||||
image.EndInit();
|
||||
image.Freeze();
|
||||
return image;
|
||||
}
|
||||
else
|
||||
|
|
@ -770,6 +760,23 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
{
|
||||
return $"{DisplayName}: {Description}";
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is Application other)
|
||||
{
|
||||
return UniqueIdentifier == other.UniqueIdentifier;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return UniqueIdentifier.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public enum PackageVersion
|
||||
|
|
@ -785,6 +792,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
{
|
||||
Read = 0x0,
|
||||
ShareExclusive = 0x10,
|
||||
ShareDenyNone = 0x40
|
||||
}
|
||||
|
||||
private enum Hresult : uint
|
||||
|
|
|
|||
|
|
@ -11,14 +11,10 @@ using Flow.Launcher.Infrastructure;
|
|||
using Flow.Launcher.Plugin.Program.Logger;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using Flow.Launcher.Plugin.SharedModels;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using System.Collections;
|
||||
using System.Diagnostics;
|
||||
using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Channels;
|
||||
using Flow.Launcher.Infrastructure.Image;
|
||||
using Flow.Launcher.Plugin.Program.Views.Models;
|
||||
using IniParser;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Program.Programs
|
||||
|
|
@ -27,10 +23,20 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
public class Win32 : IProgram, IEquatable<Win32>
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string UniqueIdentifier { get; set; }
|
||||
public string UniqueIdentifier { get => _uid; set => _uid = value == null ? string.Empty : value.ToLowerInvariant(); } // For path comparison
|
||||
public string IcoPath { get; set; }
|
||||
/// <summary>
|
||||
/// Path of the file. It's the path of .lnk and .url for .lnk and .url files.
|
||||
/// </summary>
|
||||
public string FullPath { get; set; }
|
||||
/// <summary>
|
||||
/// Path of the excutable for .lnk, or the URL for .url. Arguments are included if any.
|
||||
/// </summary>
|
||||
public string LnkResolvedPath { get; set; }
|
||||
/// <summary>
|
||||
/// Path of the actual executable file.
|
||||
/// </summary>
|
||||
public string ExecutablePath => LnkResolvedPath ?? FullPath;
|
||||
public string ParentDirectory { get; set; }
|
||||
public string ExecutableName { get; set; }
|
||||
public string Description { get; set; }
|
||||
|
|
@ -41,6 +47,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
private const string ShortcutExtension = "lnk";
|
||||
private const string UrlExtension = "url";
|
||||
private const string ExeExtension = "exe";
|
||||
private string _uid = string.Empty;
|
||||
|
||||
private static readonly Win32 Default = new Win32()
|
||||
{
|
||||
|
|
@ -100,10 +107,23 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
matchResult.MatchData = new List<int>();
|
||||
}
|
||||
|
||||
string subtitle = string.Empty;
|
||||
if (!Main._settings.HideAppsPath)
|
||||
{
|
||||
if (Extension(FullPath) == UrlExtension)
|
||||
{
|
||||
subtitle = LnkResolvedPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
subtitle = FullPath;
|
||||
}
|
||||
}
|
||||
|
||||
var result = new Result
|
||||
{
|
||||
Title = title,
|
||||
SubTitle = Main._settings.HideAppsPath ? string.Empty : LnkResolvedPath ?? FullPath,
|
||||
SubTitle = subtitle,
|
||||
IcoPath = IcoPath,
|
||||
Score = matchResult.Score,
|
||||
TitleHighlightData = matchResult.MatchData,
|
||||
|
|
@ -119,7 +139,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
|
||||
var info = new ProcessStartInfo
|
||||
{
|
||||
FileName = LnkResolvedPath ?? FullPath,
|
||||
FileName = FullPath,
|
||||
WorkingDirectory = ParentDirectory,
|
||||
UseShellExecute = true,
|
||||
Verb = runAsAdmin ? "runas" : null
|
||||
|
|
@ -200,8 +220,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
return Name;
|
||||
}
|
||||
|
||||
public static List<FileSystemWatcher> Watchers = new List<FileSystemWatcher>();
|
||||
|
||||
private static List<FileSystemWatcher> Watchers = new List<FileSystemWatcher>();
|
||||
|
||||
private static Win32 Win32Program(string path)
|
||||
{
|
||||
|
|
@ -225,11 +244,17 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
ProgramLogger.LogException($"|Win32|Win32Program|{path}" +
|
||||
$"|Permission denied when trying to load the program from {path}", e);
|
||||
|
||||
return new Win32()
|
||||
{
|
||||
Valid = false, Enabled = false
|
||||
};
|
||||
return Default;
|
||||
}
|
||||
#if !DEBUG
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramLogger.LogException($"|Win32|Win32Program|{path}" +
|
||||
"|An unexpected error occurred in the calling method Win32Program", e);
|
||||
|
||||
return Default;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private static Win32 LnkProgram(string path)
|
||||
|
|
@ -247,10 +272,15 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
var extension = Extension(target);
|
||||
if (extension == ExeExtension && File.Exists(target))
|
||||
{
|
||||
program.LnkResolvedPath = program.FullPath;
|
||||
program.FullPath = Path.GetFullPath(target).ToLower();
|
||||
program.LnkResolvedPath = Path.GetFullPath(target);
|
||||
program.ExecutableName = Path.GetFileName(target);
|
||||
|
||||
var args = _helper.arguments;
|
||||
if(!string.IsNullOrEmpty(args))
|
||||
{
|
||||
program.LnkResolvedPath += " " + args;
|
||||
}
|
||||
|
||||
var description = _helper.description;
|
||||
if (!string.IsNullOrEmpty(description))
|
||||
{
|
||||
|
|
@ -276,8 +306,14 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
"|Error caused likely due to trying to get the description of the program",
|
||||
e);
|
||||
|
||||
program.Valid = false;
|
||||
return program;
|
||||
return Default;
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
ProgramLogger.LogException($"|Win32|LnkProgram|{path}" +
|
||||
"|An unexpected error occurred in the calling method LnkProgram", e);
|
||||
|
||||
return Default;
|
||||
}
|
||||
#if !DEBUG //Only do a catch all in production. This is so make developer aware of any unhandled exception and add the exception handling in.
|
||||
catch (Exception e)
|
||||
|
|
@ -285,13 +321,12 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
ProgramLogger.LogException($"|Win32|LnkProgram|{path}" +
|
||||
"|An unexpected error occurred in the calling method LnkProgram", e);
|
||||
|
||||
program.Valid = false;
|
||||
return program;
|
||||
return Default;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private static Win32 UrlProgram(string path)
|
||||
private static Win32 UrlProgram(string path, string[] protocols)
|
||||
{
|
||||
var program = Win32Program(path);
|
||||
program.Valid = false;
|
||||
|
|
@ -306,9 +341,9 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
{
|
||||
return program;
|
||||
}
|
||||
foreach(var protocol in Main._settings.GetProtocols())
|
||||
foreach (var protocol in protocols)
|
||||
{
|
||||
if(url.StartsWith(protocol))
|
||||
if (url.StartsWith(protocol))
|
||||
{
|
||||
program.LnkResolvedPath = url;
|
||||
program.Valid = true;
|
||||
|
|
@ -340,35 +375,40 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
program.Description = info.FileDescription;
|
||||
return program;
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
ProgramLogger.LogException($"|Win32|ExeProgram|{path}" +
|
||||
$"|File not found when trying to load the program from {path}", e);
|
||||
|
||||
return Default;
|
||||
}
|
||||
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
|
||||
{
|
||||
ProgramLogger.LogException($"|Win32|ExeProgram|{path}" +
|
||||
$"|Permission denied when trying to load the program from {path}", e);
|
||||
|
||||
return new Win32()
|
||||
{
|
||||
Valid = false, Enabled = false
|
||||
};
|
||||
return Default;
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<string> ProgramPaths(string directory, string[] suffixes)
|
||||
private static IEnumerable<string> EnumerateProgramsInDir(string directory, string[] suffixes, bool recursive = true)
|
||||
{
|
||||
if (!Directory.Exists(directory))
|
||||
return Enumerable.Empty<string>();
|
||||
|
||||
return Directory.EnumerateFiles(directory, "*", new EnumerationOptions
|
||||
{
|
||||
IgnoreInaccessible = true, RecurseSubdirectories = true
|
||||
IgnoreInaccessible = true,
|
||||
RecurseSubdirectories = recursive
|
||||
}).Where(x => suffixes.Contains(Extension(x)));
|
||||
}
|
||||
|
||||
private static string Extension(string path)
|
||||
{
|
||||
var extension = Path.GetExtension(path)?.ToLower();
|
||||
var extension = Path.GetExtension(path)?.ToLowerInvariant();
|
||||
if (!string.IsNullOrEmpty(extension))
|
||||
{
|
||||
return extension.Substring(1);
|
||||
return extension.Substring(1); // remove dot
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -376,45 +416,51 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<Win32> UnregisteredPrograms(List<Settings.ProgramSource> sources, string[] suffixes)
|
||||
private static IEnumerable<Win32> UnregisteredPrograms(List<string> directories, string[] suffixes, string[] protocols)
|
||||
{
|
||||
var paths = ExceptDisabledSource(sources.Where(s => Directory.Exists(s.Location) && s.Enabled)
|
||||
.SelectMany(s => ProgramPaths(s.Location, suffixes)), x => x)
|
||||
.Distinct();
|
||||
|
||||
var programs = paths.Select(x => Extension(x) switch
|
||||
{
|
||||
ExeExtension => ExeProgram(x),
|
||||
ShortcutExtension => LnkProgram(x),
|
||||
UrlExtension => UrlProgram(x),
|
||||
_ => Win32Program(x)
|
||||
});
|
||||
// Disabled custom sources are not in DisabledProgramSources
|
||||
var paths = directories.AsParallel()
|
||||
.SelectMany(s => EnumerateProgramsInDir(s, suffixes));
|
||||
|
||||
// Remove disabled programs in DisabledProgramSources
|
||||
var programs = ExceptDisabledSource(paths).Select(x => GetProgramFromPath(x, protocols));
|
||||
return programs;
|
||||
}
|
||||
|
||||
private static IEnumerable<Win32> StartMenuPrograms(string[] suffixes)
|
||||
private static IEnumerable<Win32> StartMenuPrograms(string[] suffixes, string[] protocols)
|
||||
{
|
||||
var disabledProgramsList = Main._settings.DisabledProgramSources;
|
||||
|
||||
var directory1 = Environment.GetFolderPath(Environment.SpecialFolder.Programs);
|
||||
var directory2 = Environment.GetFolderPath(Environment.SpecialFolder.CommonPrograms);
|
||||
var paths1 = ProgramPaths(directory1, suffixes);
|
||||
var paths2 = ProgramPaths(directory2, suffixes);
|
||||
var paths1 = EnumerateProgramsInDir(directory1, suffixes);
|
||||
var paths2 = EnumerateProgramsInDir(directory2, suffixes);
|
||||
|
||||
var toFilter = paths1.Concat(paths2);
|
||||
|
||||
var programs = ExceptDisabledSource(toFilter.Distinct())
|
||||
.Select(x => Extension(x) switch
|
||||
{
|
||||
ShortcutExtension => LnkProgram(x),
|
||||
UrlExtension => UrlProgram(x),
|
||||
_ => Win32Program(x)
|
||||
});
|
||||
.Select(x => GetProgramFromPath(x, protocols));
|
||||
return programs;
|
||||
}
|
||||
|
||||
private static IEnumerable<Win32> AppPathsPrograms(string[] suffixes)
|
||||
private static IEnumerable<Win32> PATHPrograms(string[] suffixes, string[] protocols, List<string> commonParents)
|
||||
{
|
||||
var pathEnv = Environment.GetEnvironmentVariable("Path");
|
||||
if (String.IsNullOrEmpty(pathEnv))
|
||||
{
|
||||
return Array.Empty<Win32>();
|
||||
}
|
||||
|
||||
var paths = pathEnv.Split(";", StringSplitOptions.RemoveEmptyEntries).DistinctBy(p => p.ToLowerInvariant());
|
||||
|
||||
var toFilter = paths.Where(x => commonParents.All(parent => !IsSubPathOf(x, parent)))
|
||||
.AsParallel()
|
||||
.SelectMany(p => EnumerateProgramsInDir(p, suffixes, recursive: false));
|
||||
|
||||
var programs = ExceptDisabledSource(toFilter.Distinct())
|
||||
.Select(x => GetProgramFromPath(x, protocols));
|
||||
return programs;
|
||||
}
|
||||
|
||||
private static IEnumerable<Win32> AppPathsPrograms(string[] suffixes, string[] protocols)
|
||||
{
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ee872121
|
||||
const string appPaths = @"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths";
|
||||
|
|
@ -434,12 +480,11 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
toFilter = toFilter.Concat(GetPathFromRegistry(rootUser));
|
||||
}
|
||||
|
||||
|
||||
toFilter = toFilter.Distinct().Where(p => suffixes.Contains(Extension(p)));
|
||||
|
||||
var filtered = ExceptDisabledSource(toFilter);
|
||||
|
||||
return filtered.Select(GetProgramFromPath).ToList(); // ToList due to disposing issue
|
||||
var programs = ExceptDisabledSource(toFilter)
|
||||
.Select(x => GetProgramFromPath(x, protocols)).Where(x => x.Valid).ToList(); // ToList due to disposing issue
|
||||
return programs;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetPathFromRegistry(RegistryKey root)
|
||||
|
|
@ -479,24 +524,25 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
}
|
||||
}
|
||||
|
||||
private static Win32 GetProgramFromPath(string path)
|
||||
private static Win32 GetProgramFromPath(string path, string[] protocols)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return Default;
|
||||
|
||||
path = Environment.ExpandEnvironmentVariables(path);
|
||||
|
||||
if (!File.Exists(path))
|
||||
return Default;
|
||||
|
||||
var entry = Win32Program(path);
|
||||
|
||||
return entry;
|
||||
return Extension(path) switch
|
||||
{
|
||||
ShortcutExtension => LnkProgram(path),
|
||||
ExeExtension => ExeProgram(path),
|
||||
UrlExtension => UrlProgram(path, protocols),
|
||||
_ => Win32Program(path)
|
||||
}; ;
|
||||
}
|
||||
|
||||
public static IEnumerable<string> ExceptDisabledSource(IEnumerable<string> sources)
|
||||
public static IEnumerable<string> ExceptDisabledSource(IEnumerable<string> paths)
|
||||
{
|
||||
return ExceptDisabledSource(sources, x => x);
|
||||
return ExceptDisabledSource(paths, x => x.ToLowerInvariant());
|
||||
}
|
||||
|
||||
public static IEnumerable<TSource> ExceptDisabledSource<TSource>(IEnumerable<TSource> sources,
|
||||
|
|
@ -531,14 +577,16 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
|
||||
private static IEnumerable<Win32> ProgramsHasher(IEnumerable<Win32> programs)
|
||||
{
|
||||
return programs.GroupBy(p => p.FullPath.ToLower())
|
||||
// TODO: Unable to distinguish multiple lnks to the same excutable but with different params
|
||||
return programs.GroupBy(p => p.ExecutablePath.ToLowerInvariant())
|
||||
.AsParallel()
|
||||
.SelectMany(g =>
|
||||
{
|
||||
var temp = g.Where(g => !string.IsNullOrEmpty(g.Description)).ToList();
|
||||
if (temp.Any())
|
||||
return DistinctBy(temp, x => x.Description);
|
||||
return g.Take(1);
|
||||
}).ToArray();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -547,26 +595,38 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
try
|
||||
{
|
||||
var programs = Enumerable.Empty<Win32>();
|
||||
var suffixes = settings.GetSuffixes();
|
||||
var protocols = settings.GetProtocols();
|
||||
|
||||
var unregistered = UnregisteredPrograms(settings.ProgramSources, settings.GetSuffixes());
|
||||
// Disabled custom sources are not in DisabledProgramSources
|
||||
var sources = settings.ProgramSources.Where(s => Directory.Exists(s.Location) && s.Enabled).Distinct();
|
||||
var commonParents = GetCommonParents(sources);
|
||||
|
||||
var unregistered = UnregisteredPrograms(commonParents, suffixes, protocols);
|
||||
|
||||
programs = programs.Concat(unregistered);
|
||||
|
||||
var autoIndexPrograms = Enumerable.Empty<Win32>();
|
||||
var autoIndexPrograms = Enumerable.Empty<Win32>(); // for single programs, not folders
|
||||
|
||||
if (settings.EnableRegistrySource)
|
||||
{
|
||||
var appPaths = AppPathsPrograms(settings.GetSuffixes());
|
||||
var appPaths = AppPathsPrograms(suffixes, protocols);
|
||||
autoIndexPrograms = autoIndexPrograms.Concat(appPaths);
|
||||
}
|
||||
|
||||
if (settings.EnableStartMenuSource)
|
||||
{
|
||||
var startMenu = StartMenuPrograms(settings.GetSuffixes());
|
||||
var startMenu = StartMenuPrograms(suffixes, protocols);
|
||||
autoIndexPrograms = autoIndexPrograms.Concat(startMenu);
|
||||
}
|
||||
|
||||
autoIndexPrograms = ProgramsHasher(autoIndexPrograms);
|
||||
if (settings.EnablePATHSource)
|
||||
{
|
||||
var path = PATHPrograms(settings.GetSuffixes(), protocols, commonParents);
|
||||
programs = programs.Concat(path);
|
||||
}
|
||||
|
||||
autoIndexPrograms = ProgramsHasher(autoIndexPrograms).ToArray();
|
||||
|
||||
return programs.Concat(autoIndexPrograms).Where(x => x.Valid).Distinct().ToArray();
|
||||
}
|
||||
|
|
@ -600,6 +660,18 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
return UniqueIdentifier == other.UniqueIdentifier;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is Win32 other)
|
||||
{
|
||||
return UniqueIdentifier == other.UniqueIdentifier;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetStartMenuPaths()
|
||||
{
|
||||
var directory1 = Environment.GetFolderPath(Environment.SpecialFolder.Programs);
|
||||
|
|
@ -616,11 +688,13 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
if (settings.EnableStartMenuSource)
|
||||
paths.AddRange(GetStartMenuPaths());
|
||||
|
||||
paths.AddRange(from source in settings.ProgramSources where source.Enabled select source.Location);
|
||||
var customSources = GetCommonParents(settings.ProgramSources);
|
||||
paths.AddRange(customSources);
|
||||
|
||||
var fileExtensionToWatch = settings.GetSuffixes();
|
||||
foreach (var directory in from path in paths where Directory.Exists(path) select path)
|
||||
{
|
||||
WatchDirectory(directory);
|
||||
WatchDirectory(directory, fileExtensionToWatch);
|
||||
}
|
||||
|
||||
_ = Task.Run(MonitorDirectoryChangeAsync);
|
||||
|
|
@ -640,8 +714,8 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
await Task.Run(Main.IndexWin32Programs);
|
||||
}
|
||||
}
|
||||
|
||||
public static void WatchDirectory(string directory)
|
||||
|
||||
public static void WatchDirectory(string directory, string[] extensions)
|
||||
{
|
||||
if (!Directory.Exists(directory))
|
||||
{
|
||||
|
|
@ -653,7 +727,11 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
watcher.Deleted += static (_, _) => indexQueue.Writer.TryWrite(default);
|
||||
watcher.EnableRaisingEvents = true;
|
||||
watcher.IncludeSubdirectories = true;
|
||||
|
||||
foreach (var extension in extensions)
|
||||
{
|
||||
watcher.Filters.Add($"*.{extension}");
|
||||
}
|
||||
|
||||
Watchers.Add(watcher);
|
||||
}
|
||||
|
||||
|
|
@ -664,5 +742,38 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
fileSystemWatcher.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/66877016
|
||||
private static bool IsSubPathOf(string subPath, string basePath)
|
||||
{
|
||||
var rel = Path.GetRelativePath(basePath, subPath);
|
||||
return rel != "."
|
||||
&& rel != ".."
|
||||
&& !rel.StartsWith("../")
|
||||
&& !rel.StartsWith(@"..\")
|
||||
&& !Path.IsPathRooted(rel);
|
||||
}
|
||||
|
||||
private static List<string> GetCommonParents(IEnumerable<ProgramSource> programSources)
|
||||
{
|
||||
// To avoid unnecessary io
|
||||
// like c:\windows and c:\windows\system32
|
||||
var grouped = programSources.GroupBy(p => p.Location.ToLowerInvariant()[0]); // group by disk
|
||||
List<string> result = new();
|
||||
foreach (var group in grouped)
|
||||
{
|
||||
HashSet<ProgramSource> parents = group.ToHashSet();
|
||||
foreach (var source in group)
|
||||
{
|
||||
if (parents.Any(p => IsSubPathOf(source.Location, p.Location) &&
|
||||
source != p))
|
||||
{
|
||||
parents.Remove(source);
|
||||
}
|
||||
}
|
||||
result.AddRange(parents.Select(x => x.Location));
|
||||
}
|
||||
return result.DistinctBy(x => x.ToLowerInvariant()).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,26 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using Windows.Foundation.Metadata;
|
||||
using Flow.Launcher.Plugin.Program.Views.Models;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Program
|
||||
{
|
||||
public class Settings
|
||||
{
|
||||
public DateTime LastIndexTime { get; set; }
|
||||
public List<ProgramSource> ProgramSources { get; set; } = new List<ProgramSource>();
|
||||
public List<DisabledProgramSource> DisabledProgramSources { get; set; } = new List<DisabledProgramSource>();
|
||||
|
||||
[Obsolete, JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
/// <summary>
|
||||
/// User-added program sources' directories
|
||||
/// </summary>
|
||||
public List<ProgramSource> ProgramSources { get; set; } = new List<ProgramSource>();
|
||||
|
||||
/// <summary>
|
||||
/// Disabled single programs, not including User-added directories
|
||||
/// </summary>
|
||||
public List<ProgramSource> DisabledProgramSources { get; set; } = new List<ProgramSource>();
|
||||
|
||||
[Obsolete("Should use GetSuffixes() instead."), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string[] ProgramSuffixes { get; set; } = null;
|
||||
public string[] CustomSuffixes { get; set; } = Array.Empty<string>(); // Custom suffixes only
|
||||
public string[] CustomProtocols { get; set; } = Array.Empty<string>();
|
||||
|
|
@ -111,6 +118,8 @@ namespace Flow.Launcher.Plugin.Program
|
|||
public bool EnableDescription { get; set; } = false;
|
||||
public bool HideAppsPath { get; set; } = true;
|
||||
public bool EnableRegistrySource { get; set; } = true;
|
||||
public bool EnablePATHSource { get; set; } = true;
|
||||
|
||||
public string CustomizedExplorer { get; set; } = Explorer;
|
||||
public string CustomizedArgs { get; set; } = ExplorerArgs;
|
||||
|
||||
|
|
@ -119,25 +128,5 @@ namespace Flow.Launcher.Plugin.Program
|
|||
internal const string Explorer = "explorer";
|
||||
|
||||
internal const string ExplorerArgs = "%s";
|
||||
|
||||
/// <summary>
|
||||
/// Contains user added folder location contents as well as all user disabled applications
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Win32 class applications set UniqueIdentifier using their full file path</para>
|
||||
/// <para>UWP class applications set UniqueIdentifier using their Application User Model ID</para>
|
||||
/// <para>Custom user added program sources set UniqueIdentifier using their location</para>
|
||||
/// </remarks>
|
||||
public class ProgramSource
|
||||
{
|
||||
private string name;
|
||||
|
||||
public string Location { get; set; }
|
||||
public string Name { get => name ?? new DirectoryInfo(Location).Name; set => name = value; }
|
||||
public bool Enabled { get; set; } = true;
|
||||
public string UniqueIdentifier { get; set; }
|
||||
}
|
||||
|
||||
public class DisabledProgramSource : ProgramSource { }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,146 +1,88 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Flow.Launcher.Plugin.Program.Views.Models;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Program.Views.Commands
|
||||
{
|
||||
internal static class ProgramSettingDisplay
|
||||
{
|
||||
internal static List<ProgramSource> LoadProgramSources(this List<Settings.ProgramSource> programSources)
|
||||
internal static List<ProgramSource> LoadProgramSources()
|
||||
{
|
||||
var list = new List<ProgramSource>();
|
||||
|
||||
programSources.ForEach(x => list
|
||||
.Add(
|
||||
new ProgramSource
|
||||
{
|
||||
Enabled = x.Enabled,
|
||||
Location = x.Location,
|
||||
Name = x.Name,
|
||||
UniqueIdentifier = x.UniqueIdentifier
|
||||
}
|
||||
));
|
||||
|
||||
// Even though these are disabled, we still want to display them so users can enable later on
|
||||
Main._settings
|
||||
.DisabledProgramSources
|
||||
.Where(t1 => !Main._settings
|
||||
.ProgramSources // program sourcces added above already, so exlcude
|
||||
.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier))
|
||||
.Select(x => x)
|
||||
.ToList()
|
||||
.ForEach(x => list
|
||||
.Add(
|
||||
new ProgramSource
|
||||
{
|
||||
Enabled = x.Enabled,
|
||||
Location = x.Location,
|
||||
Name = x.Name,
|
||||
UniqueIdentifier = x.UniqueIdentifier
|
||||
}
|
||||
));
|
||||
|
||||
return list;
|
||||
return Main._settings
|
||||
.DisabledProgramSources
|
||||
.Union(Main._settings.ProgramSources)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
internal static void LoadAllApplications(this List<ProgramSource> list)
|
||||
internal static void DisplayAllPrograms()
|
||||
{
|
||||
Main._win32s
|
||||
.Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
|
||||
.ToList()
|
||||
.ForEach(t1 => ProgramSetting.ProgramSettingDisplayList
|
||||
.Add(
|
||||
new ProgramSource
|
||||
{
|
||||
Name = t1.Name,
|
||||
Location = t1.ParentDirectory,
|
||||
UniqueIdentifier = t1.UniqueIdentifier,
|
||||
Enabled = t1.Enabled
|
||||
}
|
||||
));
|
||||
var win32 = Main._win32s
|
||||
.Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
|
||||
.Select(x => new ProgramSource(x));
|
||||
|
||||
Main._uwps
|
||||
.Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
|
||||
.ToList()
|
||||
.ForEach(t1 => ProgramSetting.ProgramSettingDisplayList
|
||||
.Add(
|
||||
new ProgramSource
|
||||
{
|
||||
Name = t1.DisplayName,
|
||||
Location = t1.Package.Location,
|
||||
UniqueIdentifier = t1.UniqueIdentifier,
|
||||
Enabled = t1.Enabled
|
||||
}
|
||||
));
|
||||
var uwp = Main._uwps
|
||||
.Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
|
||||
.Select(x => new ProgramSource(x));
|
||||
|
||||
ProgramSetting.ProgramSettingDisplayList.AddRange(win32);
|
||||
ProgramSetting.ProgramSettingDisplayList.AddRange(uwp);
|
||||
}
|
||||
|
||||
internal static void SetProgramSourcesStatus(this List<ProgramSource> list, List<ProgramSource> selectedProgramSourcesToDisable, bool status)
|
||||
internal static void SetProgramSourcesStatus(List<ProgramSource> selectedProgramSourcesToDisable, bool status)
|
||||
{
|
||||
ProgramSetting.ProgramSettingDisplayList
|
||||
.Where(t1 => selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier && t1.Enabled != status))
|
||||
.ToList()
|
||||
.ForEach(t1 => t1.Enabled = status);
|
||||
foreach(var program in ProgramSetting.ProgramSettingDisplayList)
|
||||
{
|
||||
if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status))
|
||||
{
|
||||
program.Enabled = status;
|
||||
}
|
||||
}
|
||||
|
||||
Main._win32s
|
||||
.Where(t1 => selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier && t1.Enabled != status))
|
||||
.ToList()
|
||||
.ForEach(t1 => t1.Enabled = status);
|
||||
foreach(var program in Main._win32s)
|
||||
{
|
||||
if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status))
|
||||
{
|
||||
program.Enabled = status;
|
||||
}
|
||||
}
|
||||
|
||||
Main._uwps
|
||||
.Where(t1 => selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier && t1.Enabled != status))
|
||||
.ToList()
|
||||
.ForEach(t1 => t1.Enabled = status);
|
||||
foreach (var program in Main._uwps)
|
||||
{
|
||||
if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status))
|
||||
{
|
||||
program.Enabled = status;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void StoreDisabledInSettings(this List<ProgramSource> list)
|
||||
internal static void StoreDisabledInSettings()
|
||||
{
|
||||
Main._settings.ProgramSources
|
||||
.Where(t1 => ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier && !x.Enabled))
|
||||
.ToList()
|
||||
.ForEach(t1 => t1.Enabled = false);
|
||||
|
||||
ProgramSetting.ProgramSettingDisplayList
|
||||
// Disabled, not in DisabledProgramSources or ProgramSources
|
||||
var tmp = ProgramSetting.ProgramSettingDisplayList
|
||||
.Where(t1 => !t1.Enabled
|
||||
&& !Main._settings.DisabledProgramSources.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier))
|
||||
.ToList()
|
||||
.ForEach(x => Main._settings.DisabledProgramSources
|
||||
.Add(
|
||||
new Settings.DisabledProgramSource
|
||||
{
|
||||
Name = x.Name,
|
||||
Location = x.Location,
|
||||
UniqueIdentifier = x.UniqueIdentifier,
|
||||
Enabled = false
|
||||
}
|
||||
));
|
||||
&& !Main._settings.DisabledProgramSources.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)
|
||||
&& !Main._settings.ProgramSources.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier));
|
||||
|
||||
Main._settings.DisabledProgramSources.AddRange(tmp);
|
||||
}
|
||||
|
||||
internal static void RemoveDisabledFromSettings(this List<ProgramSource> list)
|
||||
internal static void RemoveDisabledFromSettings()
|
||||
{
|
||||
Main._settings.ProgramSources
|
||||
.Where(t1 => ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier && x.Enabled))
|
||||
.ToList()
|
||||
.ForEach(t1 => t1.Enabled = true);
|
||||
|
||||
Main._settings.DisabledProgramSources
|
||||
.Where(t1 => ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier && x.Enabled))
|
||||
.ToList()
|
||||
.ForEach(x => Main._settings.DisabledProgramSources.Remove(x));
|
||||
Main._settings.DisabledProgramSources.RemoveAll(t1 => t1.Enabled);
|
||||
}
|
||||
|
||||
internal static bool IsReindexRequired(this List<ProgramSource> selectedItems)
|
||||
{
|
||||
if (selectedItems.Where(t1 => t1.Enabled && !Main._uwps.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier)).Count() > 0
|
||||
&& selectedItems.Where(t1 => t1.Enabled && !Main._win32s.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier)).Count() > 0)
|
||||
// Not in cache
|
||||
if (selectedItems.Any(t1 => t1.Enabled && !Main._uwps.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier))
|
||||
&& selectedItems.Any(t1 => t1.Enabled && !Main._win32s.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier)))
|
||||
return true;
|
||||
|
||||
// ProgramSources holds list of user added directories,
|
||||
// so when we enable/disable we need to reindex to show/not show the programs
|
||||
// that are found in those directories.
|
||||
if (selectedItems.Where(t1 => Main._settings.ProgramSources.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier)).Count() > 0)
|
||||
if (selectedItems.Any(t1 => Main._settings.ProgramSources.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier)))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,89 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json.Serialization;
|
||||
using Flow.Launcher.Plugin.Program.Programs;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Program.Views.Models
|
||||
{
|
||||
public class ProgramSource : Settings.ProgramSource { }
|
||||
/// <summary>
|
||||
/// Contains user added folder location contents as well as all user disabled applications
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Win32 class applications set UniqueIdentifier using their full file path</para>
|
||||
/// <para>UWP class applications set UniqueIdentifier using their Application User Model ID</para>
|
||||
/// <para>Custom user added program sources set UniqueIdentifier using their location</para>
|
||||
/// </remarks>
|
||||
public class ProgramSource
|
||||
{
|
||||
private string name = string.Empty;
|
||||
private string loc = string.Empty;
|
||||
private string uniqueIdentifier = string.Empty;
|
||||
|
||||
public string Location
|
||||
{
|
||||
get => loc;
|
||||
set
|
||||
{
|
||||
loc = value ?? string.Empty;
|
||||
UniqueIdentifier = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string Name { get => name; set => name = value ?? string.Empty; }
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
public string UniqueIdentifier
|
||||
{
|
||||
get => uniqueIdentifier;
|
||||
private set
|
||||
{
|
||||
uniqueIdentifier = value == null ? string.Empty : value.ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
public ProgramSource(string name, string location, bool enabled, string uniqueIdentifier)
|
||||
{
|
||||
loc = location ?? string.Empty;
|
||||
Name = name;
|
||||
Enabled = enabled;
|
||||
UniqueIdentifier = uniqueIdentifier;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add source by location
|
||||
/// </summary>
|
||||
/// <param name="location">location of program source</param>
|
||||
/// <param name="enabled">enabled</param>
|
||||
public ProgramSource(string location, bool enabled = true)
|
||||
{
|
||||
loc = location ?? string.Empty;
|
||||
Enabled = enabled;
|
||||
UniqueIdentifier = location; // For path comparison
|
||||
Name = new DirectoryInfo(Location).Name;
|
||||
}
|
||||
|
||||
public ProgramSource(IProgram source)
|
||||
{
|
||||
loc = source.Location ?? string.Empty;
|
||||
Name = source.Name;
|
||||
Enabled = source.Enabled;
|
||||
UniqueIdentifier = source.UniqueIdentifier;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is ProgramSource other && other.UniqueIdentifier == this.UniqueIdentifier;
|
||||
}
|
||||
|
||||
public bool Equals(IProgram program)
|
||||
{
|
||||
return program != null && program.UniqueIdentifier == this.UniqueIdentifier;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(UniqueIdentifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,58 +10,89 @@
|
|||
mc:Ignorable="d">
|
||||
<Grid Margin="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="170" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="60" />
|
||||
</Grid.RowDefinitions>
|
||||
<DockPanel
|
||||
Margin="70,10,0,8"
|
||||
HorizontalAlignment="Stretch"
|
||||
LastChildFill="True">
|
||||
<TextBlock
|
||||
MinWidth="120"
|
||||
Margin="0,5,10,0"
|
||||
Text="{DynamicResource flowlauncher_plugin_program_index_source}" />
|
||||
<WrapPanel
|
||||
Width="Auto"
|
||||
Margin="0,0,14,0"
|
||||
HorizontalAlignment="Right"
|
||||
DockPanel.Dock="Right">
|
||||
<CheckBox
|
||||
Name="StartMenuEnabled"
|
||||
Margin="12,0,12,0"
|
||||
Content="{DynamicResource flowlauncher_plugin_program_index_start}"
|
||||
IsChecked="{Binding EnableStartMenuSource}"
|
||||
ToolTip="{DynamicResource flowlauncher_plugin_program_index_start_tooltip}" />
|
||||
<CheckBox
|
||||
Name="RegistryEnabled"
|
||||
Margin="12,0,12,0"
|
||||
Content="{DynamicResource flowlauncher_plugin_program_index_registry}"
|
||||
IsChecked="{Binding EnableRegistrySource}"
|
||||
ToolTip="{DynamicResource flowlauncher_plugin_program_index_registry_tooltip}" />
|
||||
|
||||
<CheckBox
|
||||
Name="PATHEnabled"
|
||||
Margin="12,0,12,0"
|
||||
Content="{DynamicResource flowlauncher_plugin_program_index_PATH}"
|
||||
IsChecked="{Binding EnablePATHSource}"
|
||||
ToolTip="{DynamicResource flowlauncher_plugin_program_index_PATH_tooltip}" />
|
||||
</WrapPanel>
|
||||
</DockPanel>
|
||||
|
||||
<StackPanel
|
||||
Grid.Row="0"
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical">
|
||||
<StackPanel Width="Auto" Orientation="Vertical">
|
||||
<StackPanel Width="Auto" Orientation="Horizontal">
|
||||
<CheckBox
|
||||
Name="StartMenuEnabled"
|
||||
Width="220"
|
||||
Margin="70,8,10,8"
|
||||
Content="{DynamicResource flowlauncher_plugin_program_index_start}"
|
||||
IsChecked="{Binding EnableStartMenuSource}"
|
||||
ToolTip="{DynamicResource flowlauncher_plugin_program_index_start_tooltip}" />
|
||||
<CheckBox
|
||||
Name="RegistryEnabled"
|
||||
Margin="70,8,10,8"
|
||||
Content="{DynamicResource flowlauncher_plugin_program_index_registry}"
|
||||
IsChecked="{Binding EnableRegistrySource}"
|
||||
ToolTip="{DynamicResource flowlauncher_plugin_program_index_registry_tooltip}" />
|
||||
</StackPanel>
|
||||
|
||||
<Separator
|
||||
Height="1"
|
||||
BorderBrush="{DynamicResource Color03B}"
|
||||
BorderThickness="1" />
|
||||
<StackPanel Width="Auto" Orientation="Horizontal">
|
||||
<Separator
|
||||
Height="1"
|
||||
BorderBrush="{DynamicResource Color03B}"
|
||||
BorderThickness="1" />
|
||||
<DockPanel
|
||||
Margin="70,10,0,8"
|
||||
HorizontalAlignment="Stretch"
|
||||
LastChildFill="True">
|
||||
<TextBlock
|
||||
MinWidth="120"
|
||||
Margin="0,5,10,0"
|
||||
Text="{DynamicResource flowlauncher_plugin_program_index_option}" />
|
||||
<WrapPanel
|
||||
Width="Auto"
|
||||
Margin="0,0,14,0"
|
||||
HorizontalAlignment="Right"
|
||||
DockPanel.Dock="Right">
|
||||
<CheckBox
|
||||
Name="HideLnkEnabled"
|
||||
Width="220"
|
||||
Margin="70,8,10,8"
|
||||
Margin="12,0,12,0"
|
||||
Content="{DynamicResource flowlauncher_plugin_program_enable_hidelnkpath}"
|
||||
IsChecked="{Binding HideAppsPath}"
|
||||
ToolTip="{DynamicResource flowlauncher_plugin_program_enable_hidelnkpath_tooltip}" />
|
||||
<CheckBox
|
||||
Name="DescriptionEnabled"
|
||||
Margin="70,8,10,8"
|
||||
Margin="12,0,12,0"
|
||||
Content="{DynamicResource flowlauncher_plugin_program_enable_description}"
|
||||
IsChecked="{Binding EnableDescription}"
|
||||
ToolTip="{DynamicResource flowlauncher_plugin_program_enable_description_tooltip}" />
|
||||
</StackPanel>
|
||||
<Separator
|
||||
Height="1"
|
||||
BorderBrush="{DynamicResource Color03B}"
|
||||
BorderThickness="1" />
|
||||
</StackPanel>
|
||||
</WrapPanel>
|
||||
</DockPanel>
|
||||
<Separator
|
||||
Height="1"
|
||||
BorderBrush="{DynamicResource Color03B}"
|
||||
BorderThickness="1" />
|
||||
<StackPanel
|
||||
Width="Auto"
|
||||
Margin="10,6,0,0"
|
||||
Height="55"
|
||||
Margin="60,6,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
|
|
@ -107,15 +138,17 @@
|
|||
</StackPanel>
|
||||
<ListView
|
||||
x:Name="programSourceView"
|
||||
Grid.Row="1"
|
||||
Margin="20,0,20,0"
|
||||
Grid.Row="2"
|
||||
Margin="70,0,20,0"
|
||||
AllowDrop="True"
|
||||
BorderBrush="DarkGray"
|
||||
BorderThickness="1"
|
||||
DragEnter="programSourceView_DragEnter"
|
||||
Drop="programSourceView_Drop"
|
||||
GridViewColumnHeader.Click="GridViewColumnHeaderClickedHandler"
|
||||
MouseDoubleClick="programSourceView_MouseDoubleClick"
|
||||
PreviewMouseRightButtonUp="ProgramSourceView_PreviewMouseRightButtonUp"
|
||||
SelectionChanged="programSourceView_SelectionChanged"
|
||||
SelectionMode="Extended">
|
||||
<ListView.View>
|
||||
<GridView>
|
||||
|
|
@ -126,7 +159,7 @@
|
|||
</DataTemplate>
|
||||
</GridViewColumn.CellTemplate>
|
||||
</GridViewColumn>
|
||||
<GridViewColumn Header="{DynamicResource flowlauncher_plugin_program_enable}">
|
||||
<GridViewColumn Header="{DynamicResource flowlauncher_plugin_program_enabled}">
|
||||
<GridViewColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock
|
||||
|
|
@ -147,7 +180,7 @@
|
|||
</ListView.View>
|
||||
</ListView>
|
||||
<DockPanel
|
||||
Grid.Row="2"
|
||||
Grid.Row="3"
|
||||
Grid.RowSpan="1"
|
||||
Margin="0,0,20,10">
|
||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
|
||||
|
|
@ -173,66 +206,3 @@
|
|||
</DockPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
|
@ -36,6 +35,7 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
_settings.EnableDescription = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HideAppsPath
|
||||
{
|
||||
get => _settings.HideAppsPath;
|
||||
|
|
@ -66,6 +66,16 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
}
|
||||
}
|
||||
|
||||
public bool EnablePATHSource
|
||||
{
|
||||
get => _settings.EnablePATHSource;
|
||||
set
|
||||
{
|
||||
_settings.EnablePATHSource = value;
|
||||
ReIndexing();
|
||||
}
|
||||
}
|
||||
|
||||
public string CustomizedExplorerPath
|
||||
{
|
||||
get => _settings.CustomizedExplorer;
|
||||
|
|
@ -88,7 +98,7 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
|
||||
private void Setting_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ProgramSettingDisplayList = _settings.ProgramSources.LoadProgramSources();
|
||||
ProgramSettingDisplayList = ProgramSettingDisplay.LoadProgramSources();
|
||||
programSourceView.ItemsSource = ProgramSettingDisplayList;
|
||||
|
||||
ViewRefresh();
|
||||
|
|
@ -147,20 +157,35 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
|
||||
private void btnEditProgramSource_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var selectedProgramSource = programSourceView.SelectedItem as Settings.ProgramSource;
|
||||
if (selectedProgramSource != null)
|
||||
{
|
||||
var add = new AddProgramSource(selectedProgramSource, _settings);
|
||||
if (add.ShowDialog() ?? false)
|
||||
{
|
||||
ReIndexing();
|
||||
}
|
||||
}
|
||||
else
|
||||
var selectedProgramSource = programSourceView.SelectedItem as ProgramSource;
|
||||
EditProgramSource(selectedProgramSource);
|
||||
}
|
||||
|
||||
private void EditProgramSource(ProgramSource selectedProgramSource)
|
||||
{
|
||||
if (selectedProgramSource == null)
|
||||
{
|
||||
string msg = context.API.GetTranslation("flowlauncher_plugin_program_pls_select_program_source");
|
||||
MessageBox.Show(msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
var add = new AddProgramSource(context, _settings, selectedProgramSource);
|
||||
if (add.ShowDialog() ?? false)
|
||||
{
|
||||
if (selectedProgramSource.Enabled)
|
||||
{
|
||||
ProgramSettingDisplay.SetProgramSourcesStatus(new List<ProgramSource> { selectedProgramSource }, true); // sync status in win32, uwp and disabled
|
||||
ProgramSettingDisplay.RemoveDisabledFromSettings();
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgramSettingDisplay.SetProgramSourcesStatus(new List<ProgramSource> { selectedProgramSource }, false);
|
||||
ProgramSettingDisplay.StoreDisabledInSettings();
|
||||
}
|
||||
ReIndexing();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void btnReindex_Click(object sender, RoutedEventArgs e)
|
||||
|
|
@ -199,19 +224,16 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
{
|
||||
foreach (string directory in directories)
|
||||
{
|
||||
if (Directory.Exists(directory) && !ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == directory))
|
||||
if (Directory.Exists(directory)
|
||||
&& !ProgramSettingDisplayList.Any(x => x.UniqueIdentifier.Equals(directory, System.StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
var source = new ProgramSource
|
||||
{
|
||||
Location = directory,
|
||||
UniqueIdentifier = directory
|
||||
};
|
||||
var source = new ProgramSource(directory);
|
||||
|
||||
directoriesToAdd.Add(source);
|
||||
}
|
||||
}
|
||||
|
||||
if (directoriesToAdd.Count() > 0)
|
||||
if (directoriesToAdd.Count > 0)
|
||||
{
|
||||
directoriesToAdd.ForEach(x => _settings.ProgramSources.Add(x));
|
||||
directoriesToAdd.ForEach(x => ProgramSettingDisplayList.Add(x));
|
||||
|
|
@ -224,7 +246,7 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
|
||||
private void btnLoadAllProgramSource_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ProgramSettingDisplayList.LoadAllApplications();
|
||||
ProgramSettingDisplay.DisplayAllPrograms();
|
||||
|
||||
ViewRefresh();
|
||||
}
|
||||
|
|
@ -235,18 +257,14 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
.SelectedItems.Cast<ProgramSource>()
|
||||
.ToList();
|
||||
|
||||
if (selectedItems.Count() == 0)
|
||||
if (selectedItems.Count == 0)
|
||||
{
|
||||
string msg = context.API.GetTranslation("flowlauncher_plugin_program_pls_select_program_source");
|
||||
MessageBox.Show(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedItems
|
||||
.Where(t1 => !_settings
|
||||
.ProgramSources
|
||||
.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier))
|
||||
.Count() == 0)
|
||||
if (IsAllItemsUserAdded(selectedItems))
|
||||
{
|
||||
var msg = string.Format(context.API.GetTranslation("flowlauncher_plugin_program_delete_program_source"));
|
||||
|
||||
|
|
@ -257,17 +275,17 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
|
||||
DeleteProgramSources(selectedItems);
|
||||
}
|
||||
else if (IsSelectedRowStatusEnabledMoreOrEqualThanDisabled(selectedItems))
|
||||
else if (HasMoreOrEqualEnabledItems(selectedItems))
|
||||
{
|
||||
ProgramSettingDisplayList.SetProgramSourcesStatus(selectedItems, false);
|
||||
ProgramSettingDisplay.SetProgramSourcesStatus(selectedItems, false);
|
||||
|
||||
ProgramSettingDisplayList.StoreDisabledInSettings();
|
||||
ProgramSettingDisplay.StoreDisabledInSettings();
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgramSettingDisplayList.SetProgramSourcesStatus(selectedItems, true);
|
||||
ProgramSettingDisplay.SetProgramSourcesStatus(selectedItems, true);
|
||||
|
||||
ProgramSettingDisplayList.RemoveDisabledFromSettings();
|
||||
ProgramSettingDisplay.RemoveDisabledFromSettings();
|
||||
}
|
||||
|
||||
if (selectedItems.IsReindexRequired())
|
||||
|
|
@ -329,35 +347,41 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
dataView.Refresh();
|
||||
}
|
||||
|
||||
private bool IsSelectedRowStatusEnabledMoreOrEqualThanDisabled(List<ProgramSource> selectedItems)
|
||||
private static bool HasMoreOrEqualEnabledItems(List<ProgramSource> items)
|
||||
{
|
||||
return selectedItems.Where(x => x.Enabled).Count() >= selectedItems.Where(x => !x.Enabled).Count();
|
||||
var enableCount = items.Where(x => x.Enabled).Count();
|
||||
return enableCount >= items.Count - enableCount;
|
||||
}
|
||||
|
||||
private void Row_OnClick(object sender, RoutedEventArgs e)
|
||||
private void programSourceView_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
var selectedItems = programSourceView
|
||||
.SelectedItems.Cast<ProgramSource>()
|
||||
.ToList();
|
||||
.SelectedItems.Cast<ProgramSource>()
|
||||
.ToList();
|
||||
|
||||
if (selectedItems
|
||||
.Where(t1 => !_settings
|
||||
.ProgramSources
|
||||
.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier))
|
||||
.Count() == 0)
|
||||
if (IsAllItemsUserAdded(selectedItems))
|
||||
{
|
||||
btnProgramSourceStatus.Content = context.API.GetTranslation("flowlauncher_plugin_program_delete");
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsSelectedRowStatusEnabledMoreOrEqualThanDisabled(selectedItems))
|
||||
else if (HasMoreOrEqualEnabledItems(selectedItems))
|
||||
{
|
||||
btnProgramSourceStatus.Content = "Disable";
|
||||
btnProgramSourceStatus.Content = context.API.GetTranslation("flowlauncher_plugin_program_disable");
|
||||
}
|
||||
else
|
||||
{
|
||||
btnProgramSourceStatus.Content = "Enable";
|
||||
btnProgramSourceStatus.Content = context.API.GetTranslation("flowlauncher_plugin_program_enable");
|
||||
}
|
||||
}
|
||||
|
||||
private void programSourceView_MouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var selectedProgramSource = programSourceView.SelectedItem as ProgramSource;
|
||||
EditProgramSource(selectedProgramSource);
|
||||
}
|
||||
|
||||
private bool IsAllItemsUserAdded(List<ProgramSource> items)
|
||||
{
|
||||
return items.All(x => _settings.ProgramSources.Any(y => y.UniqueIdentifier == x.UniqueIdentifier));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
29
appveyor.yml
29
appveyor.yml
|
|
@ -4,9 +4,17 @@ init:
|
|||
- ps: |
|
||||
$version = new-object System.Version $env:APPVEYOR_BUILD_VERSION
|
||||
$env:flowVersion = "{0}.{1}.{2}" -f $version.Major, $version.Minor, $version.Build
|
||||
if ($env:APPVEYOR_REPO_BRANCH -eq "dev")
|
||||
{
|
||||
$env:prereleaseTag = "{0}.{1}.{2}.{3}" -f $version.Major, $version.Minor, $version.Build, $version.Revision
|
||||
}
|
||||
- sc config WSearch start= auto # Starts Windows Search service- Needed for running ExplorerTest
|
||||
- net start WSearch
|
||||
|
||||
cache:
|
||||
- '%USERPROFILE%\.nuget\packages -> **.sln, **.csproj' # preserve nuget folder (packages) unless the solution or projects change
|
||||
|
||||
|
||||
assembly_info:
|
||||
patch: true
|
||||
file: SolutionAssemblyInfo.cs
|
||||
|
|
@ -14,12 +22,6 @@ assembly_info:
|
|||
assembly_file_version: $(flowVersion)
|
||||
assembly_informational_version: $(flowVersion)
|
||||
|
||||
skip_branch_with_pr: true
|
||||
|
||||
skip_commits:
|
||||
files:
|
||||
- '*.md'
|
||||
|
||||
image: Visual Studio 2022
|
||||
platform: Any CPU
|
||||
configuration: Release
|
||||
|
|
@ -28,7 +30,9 @@ before_build:
|
|||
build:
|
||||
project: Flow.Launcher.sln
|
||||
verbosity: minimal
|
||||
after_build:
|
||||
test_script:
|
||||
- dotnet test --no-build -c Release
|
||||
after_test:
|
||||
- ps: .\Scripts\post_build.ps1
|
||||
|
||||
artifacts:
|
||||
|
|
@ -51,6 +55,17 @@ deploy:
|
|||
on:
|
||||
APPVEYOR_REPO_TAG: true
|
||||
|
||||
- provider: GitHub
|
||||
repository: Flow-Launcher/Prereleases
|
||||
release: v$(prereleaseTag)
|
||||
description: 'This is the early access build of our upcoming release. All changes contained here are reviewed, tested and stable to use.\n\nSee our [release](https://github.com/Flow-Launcher/Flow.Launcher/pulls?q=is%3Aopen+is%3Apr+label%3Arelease) Pull Request for details.\n\nFor latest production release visit [here](https://github.com/Flow-Launcher/Flow.Launcher/releases/latest)\n\nPlease report any bugs or issues over at the [main repository](https://github.com/Flow-Launcher/Flow.Launcher/issues)'
|
||||
auth_token:
|
||||
secure: ij4UeXUYQBDJxn2YRAAhUOjklOGVKDB87Hn5J8tKIzj13yatoI7sLM666QDQFEgv
|
||||
artifact: Squirrel Installer, Portable Version, Squirrel nupkg, Squirrel RELEASES
|
||||
force_update: true
|
||||
on:
|
||||
branch: dev
|
||||
|
||||
- provider: GitHub
|
||||
release: v$(flowVersion)
|
||||
auth_token:
|
||||
|
|
|
|||
Loading…
Reference in a new issue