resolved merge conflicts and validated that it works as expected

This commit is contained in:
Alekhya Reddy
2020-10-27 09:39:22 -07:00
70 changed files with 1773 additions and 1067 deletions

View File

@@ -41,7 +41,7 @@ restore:
build:
commands:
# Localize the files after the build procedure to avoid existing localized files from getting overwritten. To be moved before the Build PowerToys step once the lcl files have been checked in. Tracked at https://github.com/microsoft/PowerToys/issues/6046
# Localize the files before the Build PowerToys step to generate translated resx files from the lcl files
- !!buildcommand
name: 'Localize Power Toys'
command: '.pipelines\build-localization.cmd'

173
doc/devdocs/localization.md Normal file
View File

@@ -0,0 +1,173 @@
# Localization
## Table of Contents
1. [Localization on the pipeline (CDPX)](#localization-on-the-pipeline-cdpx)
1. [UWP Special case](#uwp-special-case)
2. [Enabling localization on a new project](#enabling-localization-on-a-new-project)
1. [C++](#c)
2. [C#](#c-1)
3. [UWP](#uwp)
3. [Lcl Files](#lcl-files)
4. [Possible Issues in localization PRs (LEGO)](#possible-issues-in-localization-prs-lego)
5. [Enabling localized MSI for a new project](#enabling-localized-msi-for-a-new-project)
## Localization on the pipeline (CDPX)
[The localization step](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/pipeline.user.windows.yml#L45-L52) is run on the pipeline before the solution is built. This step runs the [build-localization](https://github.com/microsoft/PowerToys/blob/master/.pipelines/build-localization.cmd) script, which generates resx files for all the projects with localization enabled using the `Localization.XLoc` package.
The [`Localization.XLoc`](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/build-localization.cmd#L24-L25) tool is run on the repo root, and it checks for all occurrences of `LocProject.json`. Each localized project has a `LocProject.json` file in the project root, which contains the location of the English resx file, list of languages for localization, and the output path where the localized resx files are to be copied to. In addition to this, some other parameters can be set, such as whether the language ID should be added as a folder in the file path or in the file name. When the CDPX pipeline is run, the localization team is notified of changes in the English resx files. For each project with localization enabled, a `loc` folder (see [this](https://github.com/microsoft/PowerToys/tree/master/src/modules/launcher/Microsoft.Launcher/loc) for example) is created in the same directory as the `LocProject.json` file. The folder contains language specific folders which in turn have a nested folder path equivalent to `OutputPath` in the `LocProject.json`. Each of these folders contain one `lcl` file. The `lcl` files contain the English resources along with their translation for that language. These are described in more detail [here](#lcl-files). Once the `.resx` files are generated, they will be used during the `Build PowerToys` step for localized versions of the modules.
Since the localization script requires certain nuget packages, the [`restore-localization`](https://github.com/microsoft/PowerToys/blob/master/.pipelines/restore-localization.cmd) script is run before running `build-localization` to install all the required packages. This script must [run in the `restore` step](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/pipeline.user.windows.yml#L37-L39) of pipeline because [the host is network isolated](https://onebranch.visualstudio.com/Pipeline/_wiki/wikis/Pipeline.wiki/2066/Consuming-Packages-in-a-CDPx-Pipelinhttps://onebranch.visualstudio.com/Pipeline/_wiki/wikis/Pipeline.wiki/2066/Consuming-Packages-in-a-CDPx-Pipeline?anchor=overview) at the `build` step. The [Toolset package source](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/pipeline.user.windows.yml#L23) is used for this.
The process and variables that can be tweaked on the pipeline are described in more detail [here](https://onebranch.visualstudio.com/Pipeline/_wiki/wikis/Pipeline.wiki/290/Localization).
The localized resource dlls for C# projects are added to the MSI only for build on the pipeline. This is done by checking if the [`IsPipeline` variable is defined](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/installer/PowerToysSetup/Product.wxs#L804-L805), which gets defined before building the installer on the pipeline [here](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/.pipelines/build-installer.cmd#L4). This is done because the localized resx files are only present on the pipeline, and not having this check would result in the installer project failing to build locally.
### UWP Special case
C# projects normally expect localized resource files with the language id in the file name as Resources.`langId`.resx, where `langId` is generally a two character code except for language with specific variants (like zh-Hans or pt-BR):
For example, `path\Resources.resx` for English and `path\Resources.fr.resx` for French.
UWP differs from this as it expects the resources to have the same Resources.resw file name, but they should be present in language specific folders, with the full language ID (such as fr-fr, zh-hans, pt-br, etc.)
For example, `path\en-us\Resources.resw` for English and `path\fr-fr\Resources.resw` for French.
Since the pipeline generates it in this format, [a script is run](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/build-localization.cmd#L29-L31) to move these resw files to the correct format expected by all UWP projects. Currently the only UWP project is [Microsoft.PowerToys.Settings.UI](https://github.com/microsoft/PowerToys/tree/master/src/core/Microsoft.PowerToys.Settings.UI). The script used for moving the resources can be [found here](https://github.com/microsoft/PowerToys/blob/master/tools/localization/move_uwp_resources.ps1). The equivalent full language IDs for each shortened language ID obtained from the pipeline has been hardcoded in the script.
## Enabling localization on a new project
To enable localization on a new project, the first step is to create a file `LocProject.json` in the project root.
For example, for a project in the folder `src\path` where the resx file is present in `resources\Resources.resx`, the LocProject.json file will contain the following:
```
{
"Projects": [
{
"LanguageSet": "Azure_Languages",
"LocItems": [
{
"SourceFile": "src\\path\\resources\\Resources.resx",
"CopyOption": "LangIDOnName",
"OutputPath": "src\\path\\resources"
}
]
}
]
}
```
The rest of the steps depend on the project type and are covered in the sections below. The steps to add the localized files to the MSI can be found [here](#Enabling-localized-MSI-for-a-new-project).
### C++
C++ projects do not support `resx` files, and instead use `rc` files along with `resource.h` files. The CDPX pipeline however doesn't support localizing `rc` files and the other alternative they support is directly translating the resources from the binary which makes it harder to maintain resources. To avoid this, a custom script has been added which expects a resx file and converts the entries to an rc file with a string table and adds resource declarations to a resource.h file so that the resources can be compiled with the C++ project.
If you already have a .rc file, copy the string table to a separate txt file and run the [convert-stringtable-to-resx.ps1](https://github.com/microsoft/PowerToys/blob/master/tools/build/convert-stringtable-to-resx.ps1) script on it. This script is not very robust to input, and requires the data in a specific format, where `IDS_ResName L"ResourceValue"` and any number of spaces can be present in between. The script converts this file to the format expected by [`resgen`](https://docs.microsoft.com/en-us/dotnet/framework/tools/resgen-exe-resource-file-generator#Convert), which will convert it to resx. The resource names are changed from all uppercase to title case, and the `IDS_` prefix is removed. Escape characters might have to be manually replaced, for example .rc files would have escaped double quotes as `""`, so this should be replaced with just `"` before converting to the resx files.
After generating the resx file, rename the existing rc and h files to ProjName.base.rc and resource.base.h. In the rc file remove the string table which is to be localized and in the .h file remove all `#define`s corresponding to localized resources. In the vcxproj of the C++ project, add the following build event:
```
<Target Name="GenerateResourceFiles" BeforeTargets="PrepareForBuild">
<Exec Command="powershell -NonInteractive -executionpolicy Unrestricted $(SolutionDir)tools\build\convert-resx-to-rc.ps1 . resource.base.h resource.h ProjName.base.rc ProjName.rc" />
</Target>
```
This event runs a script which generates a resource.h and ProjName.rc in the `Generated Files` folder using the strings in all the resx files along with the existing information in resource.base.h and ProjName.base.rc. The script can be found [here](https://github.com/microsoft/PowerToys/blob/master/tools/build/convert-resx-to-rc.ps1). The script uses [`resgen`](https://docs.microsoft.com/en-us/dotnet/framework/tools/resgen-exe-resource-file-generator#Convert) to convert the resx file to a string table expected in the .rc file format. When the resources are added to the rc file the `IDS_` prefix is added and resource names are in upper case (as it was originally). Any occurrences of `"` in the string resource is escaped as `""` to prevent build errors. The string tables are added to the rc file in the following format:
```
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
STRINGTABLE
BEGIN
strings
END
#endif
```
Since there is no API to identify the `AFX_TARG_*`, `LANG_*` or `SUBLANG_*` values from each langId from the pipeline, these are hardcoded in the script (for each language) as done [here](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/tools/build/convert-resx-to-rc.ps1#L50-L77). **If any other languages are added in the future, this script will have to be updated.** In order to determine what are the language codes, you can open the rc file in Resource View, right click the string table and press `Insert Copy` and choose the corresponding language. This autogenerates the required code and can be used to figure out the language codes. The files also add the resource declarations to a resource.h file, starting from 101 by default(this can be changed by an optional argument). Since the output files will be generated in `Generated Files`, any includes in these two files will require an additional `..\` and wherever resource.h is used, it will have to be included as `Generated Files\resource.h`. While adding `resource.base.h` and `ProjName.base.rc` to the vcxproj, these should be modified to not participate in the build to avoid build errors:
```
<None Include="Resources.resx" />
```
Some rc/resource.h files might be used in multiple projects (for example, KBM). To ensure the projects build for these cases, the build event can be added to the entire directory so that the rc files are generated before any project is built. See [Directory.Build.targets](https://github.com/microsoft/PowerToys/blob/master/src/modules/keyboardmanager/Directory.Build.targets) for an example.
Check [this PR](https://github.com/microsoft/PowerToys/pull/6104) for an example for making these changes for a C++ project.
### C#
Since C# projects natively support `resx` files, the only step required here is to include all the resx files in the build. For .NET Core projects this is done automatically and the .csproj does not need to be modified. For other projects, the following line needs to be added:
```
<EmbeddedResource Include="Properties\Resources.*.resx" />
```
**Note:** Building with localized resources may cause a build warning `Referenced assembly 'mscorlib.dll' targets a different processor` which is a VS bug. More details can be found [here](https://github.com/microsoft/PowerToys/issues/7269).
**Note:** If a project needs to be migrated from XAML resources to resx, the easiest way to convert the resources would be to change to format to `=` separates resources by either manually (by Ctrl+H on a text editor), or by a script, and then running [`resgen`](https://docs.microsoft.com/en-us/dotnet/framework/tools/resgen-exe-resource-file-generator#Convert) on `Developer Command Prompt for VS` to convert it to resx format.
```
<system:String x:Key="wox_plugin_calculator_plugin_name">Calculator</system:String>
<system:String x:Key="wox_plugin_calculator_plugin_description">Allows to do mathematical calculations.(Try 5*3-2 in Wox)</system:String>
<system:String x:Key="wox_plugin_calculator_not_a_number">Not a number (NaN)</system:String>
```
to
```
wox_plugin_calculator_plugin_name=Calculator
wox_plugin_calculator_plugin_description=Allows to do mathematical calculations.(Try 5*3-2 in Wox)
wox_plugin_calculator_not_a_number=Not a number (NaN)
```
After adding the resx file to the project along with the resource generator, references to the strings will have to be replaced with `Properties.Resources.resName` rather than the custom APIs. Check [this PR](https://github.com/microsoft/PowerToys/pull/6165) for an example of the changes required.
### UWP
UWP projects expect `resw` files rather than `resx` (the format is almost the same). Unlike other C# projects, the files are expected in the format `fullLangId\Resources.resw`. To include these files in the build, replace the following line in the csproj:
```
<PRIResource Include="Strings\en-us\Resources.resw" />
```
to
```
<PRIResource Include="Strings\*\Resources.resw" />
```
## Lcl Files
Lcl files contain all the resources that are present in the English resx file, along with a translation if it has been added.
For example, an entry for a resource in the lcl file looks like this:
```
<Item ItemId=";EditKeyboard_WindowName" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Remap keys]]></Val>
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[Remapper des touches]]></Val>
</Tgt>
</Str>
<Disp Icon="Str" />
</Item>
```
The `<Tgt>` element would not be present in the initial commits of the lcl files, as only the English version of the string would be present.
**Note:** The CDPX Localization system has a fail-safe check on the lcl files, where if the English string value which is present inside `<Val><![CDATA[*]]></Val>` does not match the value present in the English Resources.resx file then the translated value will not be copied to the localized resx file. This is present so that obsolete translations would not be loaded when the English resource has changed, and the English string will be used rather than the obsolete translation.
## Possible Issues in localization PRs (LEGO)
Since the LEGO PRs update some of the strings in LCL files at a time, there can be multiple PRs which modify the same files, leading to merge conflicts. In most cases this would show up on GitHub as a merge conflict, but sometimes a bad git merge may occur, and the file could end up with incorrect formatting, such as two `<Tgt>` elements for a single resource. These can be fixed by ensuring the elements follow the format described in [this section](#lcl-files). To catch such errors, the build farm should be run for every LEGO PR and if any error occurs in the localization step, we should check the corresponding resx/lcl files for conflicts.
## Enabling localized MSI for a new project
For C++ and UWP projects no additional files are generated with localization that need to be added to the MSI. For C++ projects all the resources are added to the dll/exe, while for UWP projects they are added to the `resources.pri` file (which is present even for an unlocalized project). To verify if the localized resources are added to the `resources.pri` file the following steps can be done:
- Open `Developer Command Prompt for VS`
- After navigating to the folder containing the pri file, run the following command:
makepri.exe dump /if .\resources.pri
- Check the contents of the `resources.pri.xml` file that is generated from the command. The last section of the file will contain the resources with the strings in all the languages:
```
<NamedResource name="GeneralSettings_RunningAsAdminText" uri="ms-resource://f4f787a5-f0ae-47a9-be89-5408b1dd2b47/Resources/GeneralSettings_RunningAsAdminText">
<Candidate qualifiers="Language-FR" type="String">
<Value>Running as administrator</Value>
</Candidate>
<Candidate qualifiers="Language-EN-US" isDefault="true" type="String">
<Value>Running as administrator</Value>
</Candidate>
</NamedResource>
```
For C# projects, satellite dlls are generated when the project is built. For a project named `ProjName`, files are created in the format `langId\ProjName.resources.dll` where `langId` is in the same format as the lcl files. The satellite dlls need to be included with the MSI, but they must be added only if the solution is built from the build farm, as the localized resx files will not be present on local machines (and that could cause local builds of the installer to fail).
This can be done by adding the directory name of the project [here](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/installer/PowerToysSetup/Product.wxs#L806) and a resource component for the project can be created [here](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/installer/PowerToysSetup/Product.wxs#L845-L847) in this format:
```
<Component Id="ProjName_$(var.IdSafeLanguage)_Component" Directory="Resource$(var.IdSafeLanguage)ProjNameInstallFolder">
<File Id="ProjName_$(var.IdSafeLanguage)_File" Source="$(var.BinX64Dir)modules\ProjName\$(var.Language)\ProjName.resources.dll" />
</Component>
```
We should also ensure the new dlls are signed by the pipeline. Currently all dlls of the form [`*.resources.dll` are signed](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/.pipelines/pipeline.user.windows.yml#L68).
**Note:** The resource dlls should be added to the MSI project only after the initial commit with the lcl files has been done by the Localization team. Otherwise the pipeline will fail as there wouldn't be any resx files to generate the dlls.

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48" viewBox="0 0 48 48">
<g>
<path d="M45,48H25.5V45H45V25.5H25.5v-3H45V3H25.5V0H48V48ZM22.5,48H3V45H22.5V3H3V0H25.5V48ZM0,48V0H3V48Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 258 B

View File

@@ -70,24 +70,33 @@ namespace Microsoft.PowerToys.Settings.UI.Library
{
if (SettingsExists(powertoy, fileName))
{
// Given the file already exists, to deserialize the file and read it's content.
T deserializedSettings = GetFile<T>(powertoy, fileName);
// IF the file needs to be modified, to save the new configurations accordingly.
if (deserializedSettings.UpgradeSettingsConfiguration())
try
{
SaveSettings(deserializedSettings.ToJsonString(), powertoy, fileName);
// Given the file already exists, to deserialize the file and read it's content.
T deserializedSettings = GetFile<T>(powertoy, fileName);
// If the file needs to be modified, to save the new configurations accordingly.
if (deserializedSettings.UpgradeSettingsConfiguration())
{
SaveSettings(deserializedSettings.ToJsonString(), powertoy, fileName);
}
return deserializedSettings;
}
return deserializedSettings;
}
else
{
// If the settings file does not exist, to create a new object with default parameters and save it to a newly created settings file.
T newSettingsItem = new T();
SaveSettings(newSettingsItem.ToJsonString(), powertoy, fileName);
return newSettingsItem;
// Catch json deserialization exceptions when the file is corrupt and has an invalid json.
// If there are any deserialization issues like in https://github.com/microsoft/PowerToys/issues/7500, log the error and create a new settings.json file.
// This is different from the case where we have trailing zeros following a valid json file, which we have handled by trimming the trailing zeros.
catch (JsonException ex)
{
Logger.LogError($"Exception encountered while loading {powertoy} settings.", ex);
}
}
// If the settings file does not exist or if the file is corrupt, to create a new object with default parameters and save it to a newly created settings file.
T newSettingsItem = new T();
SaveSettings(newSettingsItem.ToJsonString(), powertoy, fileName);
return newSettingsItem;
}
// Given the powerToy folder name and filename to be accessed, this function deserializes and returns the file.

View File

@@ -32,6 +32,14 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Utilities
Log(message, "INFO");
}
public static void LogError(string message)
{
Log(message, "ERROR");
#if DEBUG
Debugger.Break();
#endif
}
public static void LogError(string message, Exception e)
{
Log(
@@ -42,6 +50,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Utilities
"Stack trace: " + Environment.NewLine +
e?.StackTrace,
"ERROR");
#if DEBUG
Debugger.Break();
#endif
}
private static void Log(string message, string type)

View File

@@ -86,6 +86,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
private bool _keepDateModified;
private int _encoderGuidId;
public bool IsListViewFocusRequested { get; set; }
public bool IsEnabled
{
get
@@ -257,6 +259,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
imageSizes.Add(newSize);
_advancedSizes = imageSizes;
SavesImageSizes(imageSizes);
// Set the focus requested flag to indicate that an add operation has occurred during the ContainerContentChanging event
IsListViewFocusRequested = true;
}
public void DeleteImageSize(int id)

View File

@@ -5,6 +5,7 @@
using System;
using System.Windows;
using Microsoft.PowerLauncher.Telemetry;
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
using Microsoft.PowerToys.Settings.UI.Views;
using Microsoft.PowerToys.Telemetry;
using Microsoft.Toolkit.Wpf.UI.XamlHost;
@@ -67,16 +68,17 @@ namespace Microsoft.PowerToys.Settings.UI.Runner
{
if (ShellPage.ShellHandler.IPCResponseHandleList != null)
{
try
var success = JsonObject.TryParse(msg, out JsonObject json);
if (success)
{
JsonObject json = JsonObject.Parse(msg);
foreach (Action<JsonObject> handle in ShellPage.ShellHandler.IPCResponseHandleList)
{
handle(json);
}
}
catch (Exception)
else
{
Logger.LogError("Failed to parse JSON from IPC message.");
}
}
};

View File

@@ -69,6 +69,11 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VCRTForwarders.140" Version="1.0.6" />
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers">
<Version>3.3.0</Version>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>

View File

@@ -13,7 +13,7 @@ using Windows.UI.Popups;
namespace Microsoft.PowerToys.Settings.UI.Runner
{
public class Program
public static class Program
{
// Quantity of arguments
private const int ArgumentsQty = 5;
@@ -37,9 +37,9 @@ namespace Microsoft.PowerToys.Settings.UI.Runner
App app = new App();
app.InitializeComponent();
if (args.Length >= ArgumentsQty)
if (args != null && args.Length >= ArgumentsQty)
{
int.TryParse(args[2], out int powerToysPID);
_ = int.TryParse(args[2], out int powerToysPID);
PowerToysPID = powerToysPID;
if (args[4] == "true")

View File

@@ -6,36 +6,25 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
AutomationProperties.Name="{x:Bind Header, Mode=OneTime}"
d:DesignHeight="300"
d:DesignWidth="400">
<StackPanel Orientation="Vertical">
<TextBox x:Name="HotkeyTextBox"
x:Uid="SettingsPage_SetShortcut"
AutomationProperties.HelpText="{Binding ElementName=ShortcutWarningLabelText, Path=Text}"
IsReadOnly="True">
<ToolTipService.ToolTip>
<ToolTip>
<StackPanel Orientation="Vertical">
<TextBlock x:Uid="ShortcutWarningLabel"/>
<TextBlock Text="{x:Bind Keys, Mode=OneTime}" FontWeight="SemiBold"/>
</StackPanel>
</ToolTip>
<TextBlock x:Name="ShortcutWarningLabelText">
<Run x:Uid="ShortcutWarningLabel"/>
<LineBreak/>
<Run Text="{x:Bind Keys, Mode=OneTime}" FontWeight="SemiBold"/>
</TextBlock>
</ToolTipService.ToolTip>
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="TitleText"
Text="{x:Bind Header, Mode=OneTime}"
Foreground="{Binding Path=IsEnabled, ElementName=HotkeyTextBox, Converter={StaticResource ModuleEnabledToForegroundConverter}}"
/>
<TextBlock x:Uid="SettingsPage_SetShortcut_Glyph"
x:Name="TitleGlyph" Text="&#xE946;"
FontFamily="Segoe MDL2 Assets"
Margin="4,4,0,0"
Foreground="{Binding Path=IsEnabled, ElementName=HotkeyTextBox, Converter={StaticResource ModuleEnabledToForegroundConverter}}"
/>
</StackPanel>
<TextBox x:Uid="SettingsPage_SetShortcut"
x:Name="HotkeyTextBox"
Margin="0,5,0,0"
IsReadOnly="True"
/>
</StackPanel>
<TextBox.Header>
<TextBlock>
<Run Text="{x:Bind Header, Mode=OneTime}"/>
<Run Text="&#xE946;" FontFamily="Segoe MDL2 Assets"/>
</TextBlock>
</TextBox.Header>
</TextBox>
</UserControl>

View File

@@ -62,10 +62,10 @@
IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled}"
AutomationProperties.LabeledBy="{Binding ElementName=FancyZones_LaunchEditorButtonControl}">
<StackPanel Orientation="Horizontal">
<Viewbox Height="12" Width="12">
<PathIcon Data="M896 0v896H0V0h896zM768 768V128H128v640h640zM0 1920v-896h1920v896H0zm128-768v640h1664v-640H128zM1024 0h896v896h-896V0zm768 768V128h-640v640h640z"/>
<Viewbox Height="14" Width="14" Margin="-1,1,0,0">
<PathIcon Data="M45,48H25.5V45H45V25.5H25.5v-3H45V3H25.5V0H48V48ZM22.5,48H3V45H22.5V3H3V0H25.5V48ZM0,48V0H3V48Z"/>
</Viewbox>
<TextBlock Margin="12,0,0,0"
<TextBlock Margin="8,0,0,0"
Name="FancyZones_LaunchEditorButtonControl"
x:Uid="FancyZones_LaunchEditorButtonControl"/>
</StackPanel>

View File

@@ -89,21 +89,19 @@
<!-- Replaced the Radiobuttons parent control with a StackPanel to mitigate the Tab and Arrow key related keyboard navigation issues due to XAML Islands
Tracking issue in the winui repository - https://github.com/microsoft/microsoft-ui-xaml/issues/3156 -->
<TextBlock Name="RadioButtons_Name_Theme"
x:Uid="RadioButtons_Name_Theme"
<TextBlock x:Name="RadioButtons_Name_Theme"
x:Uid="ColorModeHeader"
Margin="{StaticResource SmallTopMargin}"/>
<StackPanel AutomationProperties.LabeledBy="{Binding ElementName=RadioButtons_Name_Theme}">
<RadioButton x:Uid="GeneralPage_Radio_Theme_Dark"
Content="Dark"
<RadioButton x:Uid="Radio_Theme_Dark"
IsChecked="{ Binding Mode=TwoWay, Path=IsDarkThemeRadioButtonChecked}"/>
<RadioButton x:Uid="GeneralPage_Radio_Theme_Light"
Content="Light"
<RadioButton x:Uid="Radio_Theme_Light"
IsChecked="{ Binding Mode=TwoWay, Path=IsLightThemeRadioButtonChecked}"/>
<RadioButton x:Uid="GeneralPage_Radio_Theme_Default"
Content="System default"
<RadioButton x:Uid="Radio_Theme_Default"
IsChecked="{ Binding Mode=TwoWay, Path=IsSystemThemeRadioButtonChecked}"/>
<HyperlinkButton x:Uid="Windows_Color_Settings" NavigateUri="ms-settings:colors"/>
</StackPanel>
<ToggleSwitch x:Uid="GeneralPage_ToggleSwitch_RunAtStartUp"

View File

@@ -65,7 +65,8 @@
SelectionMode="None"
ScrollViewer.HorizontalScrollMode="Enabled"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.IsHorizontalRailEnabled="True">
ScrollViewer.IsHorizontalRailEnabled="True"
ContainerContentChanging="ImagesSizesListView_ContainerContentChanging">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
@@ -277,7 +278,9 @@
x:Uid="ImageResizer_FilenameFormatPlaceholder"
IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled}"
Margin="{StaticResource SmallTopMargin}"
AutomationProperties.LabeledBy="{Binding ElementName=ImageResizer_FilenameFormatHeader}">
AutomationProperties.LabeledBy="{Binding ElementName=ImageResizer_FilenameFormatHeader}"
AutomationProperties.HelpText="{Binding ElementName=FileFormatTextBlock, Path=Text}"
>
<TextBox.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Name="ImageResizer_FilenameFormatHeader"
@@ -290,44 +293,34 @@
</StackPanel>
</TextBox.Header>
<ToolTipService.ToolTip>
<StackPanel>
<TextBlock x:Uid="ImageResizer_FileFormatDescription"/>
<TextBlock Margin="{StaticResource SmallTopMargin}">
<Run Text="%1" FontWeight="Bold" />
<Run Text=" - "/>
<Run x:Uid="ImageResizer_Formatting_Filename" />
</TextBlock>
<TextBlock>
<Run Text="%2" FontWeight="Bold" />
<Run Text=" - "/>
<Run x:Uid="ImageResizer_Formatting_Sizename"/>
</TextBlock>
<TextBlock>
<Run Text="%3" FontWeight="Bold" />
<Run Text=" - "/>
<Run x:Uid="ImageResizer_Formatting_SelectedWidth"/>
</TextBlock>
<TextBlock>
<Run Text="%4" FontWeight="Bold" />
<Run Text=" - "/>
<Run x:Uid="ImageResizer_Formatting_SelectedHeight"/>
</TextBlock>
<TextBlock>
<Run Text="%5" FontWeight="Bold" />
<Run Text=" - "/>
<Run x:Uid="ImageResizer_Formatting_ActualWidth"/>
</TextBlock>
<TextBlock>
<Run Text="%6" FontWeight="Bold" />
<Run Text=" - "/>
<Run x:Uid="ImageResizer_Formatting_ActualHeight"/>
</TextBlock>
</StackPanel>
<TextBlock x:Name="FileFormatTextBlock">
<Run x:Uid="ImageResizer_FileFormatDescription"/>
<LineBreak/>
<LineBreak/>
<Run Text="%1" FontWeight="Bold" />
<Run Text=" - "/>
<Run x:Uid="ImageResizer_Formatting_Filename" />
<LineBreak/>
<Run Text="%2" FontWeight="Bold" />
<Run Text=" - "/>
<Run x:Uid="ImageResizer_Formatting_Sizename"/>
<LineBreak/>
<Run Text="%3" FontWeight="Bold" />
<Run Text=" - "/>
<Run x:Uid="ImageResizer_Formatting_SelectedWidth"/>
<LineBreak/>
<Run Text="%4" FontWeight="Bold" />
<Run Text=" - "/>
<Run x:Uid="ImageResizer_Formatting_SelectedHeight"/>
<LineBreak/>
<Run Text="%5" FontWeight="Bold" />
<Run Text=" - "/>
<Run x:Uid="ImageResizer_Formatting_ActualWidth"/>
<LineBreak/>
<Run Text="%6" FontWeight="Bold" />
<Run Text=" - "/>
<Run x:Uid="ImageResizer_Formatting_ActualHeight"/>
</TextBlock>
</ToolTipService.ToolTip>
</TextBox>

View File

@@ -2,6 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Linq;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
using Microsoft.PowerToys.Settings.UI.Library.ViewModels;
@@ -45,5 +46,18 @@ namespace Microsoft.PowerToys.Settings.UI.Views
{
}
}
private void ImagesSizesListView_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
{
if (ViewModel.IsListViewFocusRequested)
{
// Set focus to the last item in the ListView
int size = ImagesSizesListView.Items.Count;
((ListViewItem)ImagesSizesListView.ContainerFromIndex(size - 1)).Focus(FocusState.Programmatic);
// Reset the focus requested flag
ViewModel.IsListViewFocusRequested = false;
}
}
}
}

View File

@@ -146,19 +146,28 @@
<TextBlock x:Uid="Appearance_GroupSettings"
Style="{StaticResource SettingsGroupTitleStyle}"
Foreground="{x:Bind Mode=OneWay, Path=ViewModel.EnablePowerLauncher, Converter={StaticResource ModuleEnabledToForegroundConverter}}" />
<TextBlock x:Uid="PowerLauncher_Theme"
<!-- We cannot navigate to all the radio buttons using the arrow keys because of an XYNavigation issue in the RadioButtons control.
The screen reader does not read the heading when we tab into a radio button, even though the LabeledBy automation property is set.
Link to the issue in the winui repository - https://github.com/microsoft/microsoft-ui-xaml/issues/3156 -->
<TextBlock x:Uid="ColorModeHeader"
x:Name="RadioButtons_Name_Theme"
Margin="{StaticResource SmallTopMargin}"
Foreground="{x:Bind Mode=OneWay, Path=ViewModel.EnablePowerLauncher, Converter={StaticResource ModuleEnabledToForegroundConverter}}" />
<muxc:RadioButtons IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.EnablePowerLauncher}">
<RadioButton x:Uid="PowerLauncher_Radio_Theme_Dark"
<StackPanel AutomationProperties.LabeledBy="{Binding ElementName=RadioButtons_Name_Theme}">
<RadioButton x:Uid="Radio_Theme_Dark"
IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.EnablePowerLauncher}"
IsChecked="{Binding Mode=TwoWay, Path=IsDarkThemeRadioButtonChecked}" />
<RadioButton x:Uid="PowerLauncher_Radio_Theme_Light"
<RadioButton x:Uid="Radio_Theme_Light"
IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.EnablePowerLauncher}"
IsChecked="{Binding Mode=TwoWay, Path=IsLightThemeRadioButtonChecked}" />
<RadioButton x:Uid="PowerLauncher_Radio_Theme_Default"
<RadioButton x:Uid="Radio_Theme_Default"
IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.EnablePowerLauncher}"
IsChecked="{Binding Mode=TwoWay, Path=IsSystemThemeRadioButtonChecked}" />
</muxc:RadioButtons>
<HyperlinkButton x:Uid="Windows_Color_Settings" NavigateUri="ms-settings:colors"/>
</StackPanel>
</StackPanel>
<RelativePanel x:Name="SidePanel"

View File

@@ -41,10 +41,9 @@
</winui:NavigationViewItem.Icon>
</winui:NavigationViewItem>
<!-- TO DO: Update icon -->
<winui:NavigationViewItem x:Uid="Shell_FancyZones" helpers:NavHelper.NavigateTo="views:FancyZonesPage" AutomationProperties.HeadingLevel="Level1">
<winui:NavigationViewItem.Icon>
<PathIcon Data="M896 0v896H0V0h896zM768 768V128H128v640h640zM0 1920v-896h1920v896H0zm128-768v640h1664v-640H128zM1024 0h896v896h-896V0zm768 768V128h-640v640h640z"></PathIcon>
<PathIcon Data="M45,48H25.5V45H45V25.5H25.5v-3H45V3H25.5V0H48V48ZM22.5,48H3V45H22.5V3H3V0H25.5V48ZM0,48V0H3V48Z"/>
</winui:NavigationViewItem.Icon>
</winui:NavigationViewItem>
@@ -54,35 +53,30 @@
</winui:NavigationViewItem.Icon>
</winui:NavigationViewItem>
<!-- TO DO: Update icon -->
<winui:NavigationViewItem x:Uid="Shell_ImageResizer" helpers:NavHelper.NavigateTo="views:ImageResizerPage" AutomationProperties.HeadingLevel="Level1">
<winui:NavigationViewItem.Icon>
<PathIcon Data="M0 768h1408v1152H0V768zm128 1024h870l-582-581-288 288v293zm1152 0v-102l-224-223-101 101 223 224h102zM128 896v421l288-287 448 447 192-191 224 224V896H128zm832 256q-26 0-45-19t-19-45q0-26 19-45t45-19q26 0 45 19t19 45q0 26-19 45t-45 19zm960-512V347l-339 338-90-90 338-339h-293V128h512v512h-128zm-768-512h256v128h-256V128zm-128 128H768V128h256v128zm-384 0H384V128h256v128zm-384 0H0V128h256v128zM128 640H0V384h128v256zm1920 128v256h-128V768h128zm-128 384h128v256h-128v-256zm0 384h128v256h-128v-256zm-384 256h256v128h-256v-128z"></PathIcon>
</winui:NavigationViewItem.Icon>
</winui:NavigationViewItem>
<!-- TO DO: Update icon -->
<winui:NavigationViewItem x:Uid="Shell_KeyboardManager" helpers:NavHelper.NavigateTo="views:KeyboardManagerPage" AutomationProperties.HeadingLevel="Level1">
<winui:NavigationViewItem.Icon>
<FontIcon Glyph="&#xE765;"/>
</winui:NavigationViewItem.Icon>
</winui:NavigationViewItem>
<!-- TO DO: Update icon -->
<winui:NavigationViewItem x:Uid="Shell_PowerRename" helpers:NavHelper.NavigateTo="views:PowerRenamePage" AutomationProperties.HeadingLevel="Level1">
<winui:NavigationViewItem.Icon>
<FontIcon Glyph="&#xE8AC;"/>
</winui:NavigationViewItem.Icon>
</winui:NavigationViewItem>
<!-- TO DO: Update icon -->
<winui:NavigationViewItem x:Uid="Shell_PowerLauncher" helpers:NavHelper.NavigateTo="views:PowerLauncherPage" AutomationProperties.HeadingLevel="Level1">
<winui:NavigationViewItem.Icon>
<FontIcon Glyph="&#xE773;"/>
</winui:NavigationViewItem.Icon>
</winui:NavigationViewItem>
<!-- TO DO: Update icon -->
<winui:NavigationViewItem x:Uid="Shell_ShortcutGuide" helpers:NavHelper.NavigateTo="views:ShortcutGuidePage" AutomationProperties.HeadingLevel="Level1">
<winui:NavigationViewItem.Icon>
<FontIcon Glyph="&#xEDA7;"/>
@@ -94,7 +88,6 @@
DefaultHeader="{x:Bind ViewModel.Selected.Content, Mode=OneWay}">
<behaviors:NavigationViewHeaderBehavior.DefaultHeaderTemplate>
<DataTemplate>
<!-- TODO: Style clean up-->
<Grid Margin="0, -2, 0, 6">
<TextBlock
Text="{Binding}"

View File

@@ -90,17 +90,18 @@
The screen reader does not read the heading when we tab into a radio button, even though the LabeledBy automation property is set.
Link to the issue in the winui repository - https://github.com/microsoft/microsoft-ui-xaml/issues/3156 -->
<TextBlock Name="ShortcutGuide_Theme"
x:Uid="ShortcutGuide_Theme"
x:Uid="ColorModeHeader"
Margin="{StaticResource SmallTopMargin}"
Foreground="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled, Converter={StaticResource ModuleEnabledToForegroundConverter}}"/>
<muxc:RadioButtons IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled}"
SelectedIndex="{x:Bind Mode=TwoWay, Path=ViewModel.ThemeIndex}"
Margin="{StaticResource XXSmallTopMargin}"
AutomationProperties.LabeledBy="{Binding ElementName=ShortcutGuide_Theme}">
<RadioButton x:Uid="GeneralPage_Radio_Theme_Dark" />
<RadioButton x:Uid="GeneralPage_Radio_Theme_Light" />
<RadioButton x:Uid="GeneralPage_Radio_Theme_Default"/>
<RadioButton x:Uid="Radio_Theme_Dark" />
<RadioButton x:Uid="Radio_Theme_Light" />
<RadioButton x:Uid="Radio_Theme_Default"/>
</muxc:RadioButtons>
<HyperlinkButton x:Uid="Windows_Color_Settings" NavigateUri="ms-settings:colors"/>
</StackPanel>
<RelativePanel x:Name="SidePanel"

View File

@@ -24,6 +24,7 @@ namespace ColorPicker.Mouse
private readonly IUserSettings _userSettings;
private System.Windows.Point _previousMousePosition = new System.Windows.Point(-1, 1);
private Color _previousColor = Color.Transparent;
private bool _colorFormatChanged;
[ImportingConstructor]
public MouseInfoProvider(AppStateHandler appStateMonitor, IUserSettings userSettings)
@@ -40,6 +41,7 @@ namespace ColorPicker.Mouse
_mouseHook = new MouseHook();
_userSettings = userSettings;
_userSettings.CopiedColorRepresentation.PropertyChanged += CopiedColorRepresentation_PropertyChanged;
}
public event EventHandler<Color> MouseColorChanged;
@@ -73,9 +75,10 @@ namespace ColorPicker.Mouse
}
var color = GetPixelColor(mousePosition);
if (_previousColor != color)
if (_previousColor != color || _colorFormatChanged)
{
_previousColor = color;
_colorFormatChanged = false;
MouseColorChanged?.Invoke(this, color);
}
}
@@ -137,6 +140,11 @@ namespace ColorPicker.Mouse
OnMouseDown?.Invoke(this, p);
}
private void CopiedColorRepresentation_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
_colorFormatChanged = true;
}
private void DisposeHook()
{
if (_timer.IsEnabled)

View File

@@ -37,6 +37,15 @@
</Str>
<Disp Icon="Str" />
</Item>
<Item ItemId=";Canvas_Layout_Editor" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Canvas layout editor]]></Val>
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[Layout-Editor für Canvas]]></Val>
</Tgt>
</Str>
<Disp Icon="Str" />
</Item>
<Item ItemId=";Choose_Layout" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Choose your layout for this desktop]]></Val>
@@ -118,6 +127,15 @@
</Str>
<Disp Icon="Str" />
</Item>
<Item ItemId=";Fancy_Zones_Main_Editor" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[FancyZones main editor]]></Val>
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[FancyZones-Haupteditor]]></Val>
</Tgt>
</Str>
<Disp Icon="Str" />
</Item>
<Item ItemId=";Name" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Name]]></Val>

View File

@@ -37,6 +37,15 @@
</Str>
<Disp Icon="Str" />
</Item>
<Item ItemId=";Canvas_Layout_Editor" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Canvas layout editor]]></Val>
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[Editor de diseño de lienzo]]></Val>
</Tgt>
</Str>
<Disp Icon="Str" />
</Item>
<Item ItemId=";Choose_Layout" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Choose your layout for this desktop]]></Val>
@@ -118,6 +127,15 @@
</Str>
<Disp Icon="Str" />
</Item>
<Item ItemId=";Fancy_Zones_Main_Editor" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[FancyZones main editor]]></Val>
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[Editor principal de FancyZones]]></Val>
</Tgt>
</Str>
<Disp Icon="Str" />
</Item>
<Item ItemId=";Name" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Name]]></Val>

View File

@@ -37,6 +37,15 @@
</Str>
<Disp Icon="Str" />
</Item>
<Item ItemId=";Canvas_Layout_Editor" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Canvas layout editor]]></Val>
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[Éditeur de disposition de canevas]]></Val>
</Tgt>
</Str>
<Disp Icon="Str" />
</Item>
<Item ItemId=";Choose_Layout" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Choose your layout for this desktop]]></Val>
@@ -118,6 +127,15 @@
</Str>
<Disp Icon="Str" />
</Item>
<Item ItemId=";Fancy_Zones_Main_Editor" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[FancyZones main editor]]></Val>
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[Éditeur principal de FancyZones]]></Val>
</Tgt>
</Str>
<Disp Icon="Str" />
</Item>
<Item ItemId=";Name" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Name]]></Val>

View File

@@ -37,6 +37,15 @@
</Str>
<Disp Icon="Str" />
</Item>
<Item ItemId=";Canvas_Layout_Editor" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Canvas layout editor]]></Val>
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[Editor voor canvasindeling]]></Val>
</Tgt>
</Str>
<Disp Icon="Str" />
</Item>
<Item ItemId=";Choose_Layout" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Choose your layout for this desktop]]></Val>
@@ -118,6 +127,15 @@
</Str>
<Disp Icon="Str" />
</Item>
<Item ItemId=";Fancy_Zones_Main_Editor" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[FancyZones main editor]]></Val>
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[FancyZones-hoofdeditor]]></Val>
</Tgt>
</Str>
<Disp Icon="Str" />
</Item>
<Item ItemId=";Name" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Name]]></Val>

View File

@@ -37,6 +37,15 @@
</Str>
<Disp Icon="Str" />
</Item>
<Item ItemId=";Canvas_Layout_Editor" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Canvas layout editor]]></Val>
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[Editor de layout de tela]]></Val>
</Tgt>
</Str>
<Disp Icon="Str" />
</Item>
<Item ItemId=";Choose_Layout" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Choose your layout for this desktop]]></Val>
@@ -118,6 +127,15 @@
</Str>
<Disp Icon="Str" />
</Item>
<Item ItemId=";Fancy_Zones_Main_Editor" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[FancyZones main editor]]></Val>
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[Editor principal do FancyZones]]></Val>
</Tgt>
</Str>
<Disp Icon="Str" />
</Item>
<Item ItemId=";Name" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Name]]></Val>

View File

@@ -37,6 +37,15 @@
</Str>
<Disp Icon="Str" />
</Item>
<Item ItemId=";Canvas_Layout_Editor" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Canvas layout editor]]></Val>
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[Tuval düzeni düzenleyicisi]]></Val>
</Tgt>
</Str>
<Disp Icon="Str" />
</Item>
<Item ItemId=";Choose_Layout" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Choose your layout for this desktop]]></Val>
@@ -118,6 +127,15 @@
</Str>
<Disp Icon="Str" />
</Item>
<Item ItemId=";Fancy_Zones_Main_Editor" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[FancyZones main editor]]></Val>
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[FancyZones ana düzenleyicisi]]></Val>
</Tgt>
</Str>
<Disp Icon="Str" />
</Item>
<Item ItemId=";Name" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Name]]></Val>

View File

@@ -616,7 +616,7 @@ void FancyZones::ToggleEditor() noexcept
winrt::com_ptr<IZoneWindow> zoneWindow;
std::shared_lock readLock(m_lock);
if (m_settings->GetSettings()->spanZonesAcrossMonitors)
{
zoneWindow = m_workAreaHandler.GetWorkArea(m_currentDesktopId, NULL);
@@ -625,7 +625,7 @@ void FancyZones::ToggleEditor() noexcept
{
zoneWindow = m_workAreaHandler.GetWorkArea(m_currentDesktopId, monitor);
}
if (!zoneWindow)
{
return;
@@ -639,7 +639,8 @@ void FancyZones::ToggleEditor() noexcept
m_dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] {
allMonitors = FancyZonesUtils::GetAllMonitorRects<&MONITORINFOEX::rcWork>();
} }).wait();
} })
.wait();
UINT currentDpi = 0;
for (const auto& monitor : allMonitors)
@@ -650,15 +651,15 @@ void FancyZones::ToggleEditor() noexcept
{
if (currentDpi == 0)
{
currentDpi = dpiX;
continue;
currentDpi = dpiX;
continue;
}
if (currentDpi != dpiX)
{
MessageBoxW(NULL,
GET_RESOURCE_STRING(IDS_SPAN_ACROSS_ZONES_WARNING).c_str(),
GET_RESOURCE_STRING(IDS_POWERTOYS_FANCYZONES).c_str(),
MB_OK | MB_ICONWARNING);
GET_RESOURCE_STRING(IDS_SPAN_ACROSS_ZONES_WARNING).c_str(),
GET_RESOURCE_STRING(IDS_POWERTOYS_FANCYZONES).c_str(),
MB_OK | MB_ICONWARNING);
break;
}
}
@@ -693,8 +694,9 @@ void FancyZones::ToggleEditor() noexcept
mi.cbSize = sizeof(mi);
m_dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] {
GetMonitorInfo(monitor, &mi);
} }).wait();
GetMonitorInfo(monitor, &mi);
} })
.wait();
const auto x = mi.rcWork.left;
const auto y = mi.rcWork.top;
@@ -981,7 +983,7 @@ void FancyZones::UpdateZoneWindows() noexcept
auto callback = [](HMONITOR monitor, HDC, RECT*, LPARAM data) -> BOOL {
capture* params = reinterpret_cast<capture*>(data);
MONITORINFOEX mi{ { .cbSize = sizeof(mi)} };
MONITORINFOEX mi{ { .cbSize = sizeof(mi) } };
if (GetMonitorInfoW(monitor, &mi))
{
auto& displayDeviceIdxMap = *(params->displayDeviceIdx);
@@ -1006,8 +1008,8 @@ void FancyZones::UpdateZoneWindows() noexcept
if (deviceId.empty())
{
deviceId = GetSystemMetrics(SM_REMOTESESSION) ?
L"\\\\?\\DISPLAY#REMOTEDISPLAY#" :
L"\\\\?\\DISPLAY#LOCALDISPLAY#";
L"\\\\?\\DISPLAY#REMOTEDISPLAY#" :
L"\\\\?\\DISPLAY#LOCALDISPLAY#";
fancyZones->AddZoneWindow(monitor, deviceId);
}
@@ -1180,21 +1182,24 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept
else
{
auto workArea = m_workAreaHandler.GetWorkArea(m_currentDesktopId, monitor);
auto zoneSet = workArea->ActiveZoneSet();
if (zoneSet)
if (workArea)
{
const auto zones = zoneSet->GetZones();
for (const auto& [zoneId, zone] : zones)
auto zoneSet = workArea->ActiveZoneSet();
if (zoneSet)
{
RECT zoneRect = zone->GetZoneRect();
const auto zones = zoneSet->GetZones();
for (const auto& [zoneId, zone] : zones)
{
RECT zoneRect = zone->GetZoneRect();
zoneRect.left += monitorRect.left;
zoneRect.right += monitorRect.left;
zoneRect.top += monitorRect.top;
zoneRect.bottom += monitorRect.top;
zoneRect.left += monitorRect.left;
zoneRect.right += monitorRect.left;
zoneRect.top += monitorRect.top;
zoneRect.bottom += monitorRect.top;
zoneRects.emplace_back(zoneRect);
zoneRectsInfo.emplace_back(zoneId, workArea);
zoneRects.emplace_back(zoneRect);
zoneRectsInfo.emplace_back(zoneId, workArea);
}
}
}
}
@@ -1224,21 +1229,24 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept
if (currentMonitorRect.top <= currentMonitorRect.bottom)
{
auto workArea = m_workAreaHandler.GetWorkArea(m_currentDesktopId, current);
auto zoneSet = workArea->ActiveZoneSet();
if (zoneSet)
if (workArea)
{
const auto zones = zoneSet->GetZones();
for (const auto& [zoneId, zone] : zones)
auto zoneSet = workArea->ActiveZoneSet();
if (zoneSet)
{
RECT zoneRect = zone->GetZoneRect();
const auto zones = zoneSet->GetZones();
for (const auto& [zoneId, zone] : zones)
{
RECT zoneRect = zone->GetZoneRect();
zoneRect.left += currentMonitorRect.left;
zoneRect.right += currentMonitorRect.left;
zoneRect.top += currentMonitorRect.top;
zoneRect.bottom += currentMonitorRect.top;
zoneRect.left += currentMonitorRect.left;
zoneRect.right += currentMonitorRect.left;
zoneRect.top += currentMonitorRect.top;
zoneRect.bottom += currentMonitorRect.top;
zoneRects.emplace_back(zoneRect);
zoneRectsInfo.emplace_back(zoneId, workArea);
zoneRects.emplace_back(zoneRect);
zoneRectsInfo.emplace_back(zoneId, workArea);
}
}
}
}
@@ -1351,9 +1359,9 @@ bool FancyZones::ShouldProcessSnapHotkey(DWORD vkCode) noexcept
{
monitor = MonitorFromWindow(GetForegroundWindow(), MONITOR_DEFAULTTONULL);
}
auto zoneWindow = m_workAreaHandler.GetWorkArea(m_currentDesktopId, monitor);
if (zoneWindow->ActiveZoneSet() != nullptr)
if (zoneWindow && zoneWindow->ActiveZoneSet() != nullptr)
{
if (vkCode == VK_UP || vkCode == VK_DOWN)
{

View File

@@ -307,9 +307,15 @@ void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMan
// Whenever a remap is added move to the bottom of the screen
scrollViewer.ChangeView(nullptr, scrollViewer.ScrollableHeight(), nullptr);
});
// Set accessible name for the addRemapKey button
addRemapKey.SetValue(Automation::AutomationProperties::NameProperty(), box_value(GET_RESOURCE_STRING(IDS_ADD_KEY_REMAP_BUTTON)));
// Add tooltip for add button which would appear on hover
ToolTip addRemapKeytoolTip;
addRemapKeytoolTip.Content(box_value(GET_RESOURCE_STRING(IDS_ADD_KEY_REMAP_BUTTON)));
ToolTipService::SetToolTip(addRemapKey, addRemapKeytoolTip);
// Header and example text at the top of the window
StackPanel helperText;
helperText.Children().Append(keyRemapInfoHeader);

View File

@@ -294,9 +294,15 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa
// Whenever a remap is added move to the bottom of the screen
scrollViewer.ChangeView(nullptr, scrollViewer.ScrollableHeight(), nullptr);
});
// Set accessible name for the add shortcut button
addShortcut.SetValue(Automation::AutomationProperties::NameProperty(), box_value(GET_RESOURCE_STRING(IDS_ADD_SHORTCUT_BUTTON)));
// Add tooltip for add button which would appear on hover
ToolTip addShortcuttoolTip;
addShortcuttoolTip.Content(box_value(GET_RESOURCE_STRING(IDS_ADD_SHORTCUT_BUTTON)));
ToolTipService::SetToolTip(addShortcut, addShortcuttoolTip);
// Header and example text at the top of the window
StackPanel helperText;
helperText.Children().Append(shortcutRemapInfoHeader);

View File

@@ -105,6 +105,12 @@ void ShortcutControl::AddNewShortcutControlRow(Grid& parent, std::vector<std::ve
targetAppTextBox.PlaceholderText(KeyboardManagerConstants::DefaultAppName);
targetAppTextBox.Text(targetAppName);
// GotFocus handler will be called whenever the user tabs into or clicks on the textbox
targetAppTextBox.GotFocus([targetAppTextBox](auto const& sender, auto const& e) {
// Select all text for accessible purpose
targetAppTextBox.SelectAll();
});
// LostFocus handler will be called whenever text is updated by a user and then they click something else or tab to another control. Does not get called if Text is updated while the TextBox isn't in focus (i.e. from code)
targetAppTextBox.LostFocus([&keyboardRemapControlObjects, parent, targetAppTextBox](auto const& sender, auto const& e) {
// Get index of targetAppTextBox button
@@ -226,8 +232,15 @@ void ShortcutControl::AddNewShortcutControlRow(Grid& parent, std::vector<std::ve
// delete the ShortcutControl objects so that they get destructed
keyboardRemapControlObjects.erase(keyboardRemapControlObjects.begin() + bufferIndex);
});
// To set the accessible name of the delete button
deleteShortcut.SetValue(Automation::AutomationProperties::NameProperty(), box_value(GET_RESOURCE_STRING(IDS_DELETE_REMAPPING_BUTTON)));
// Add tooltip for delete button which would appear on hover
ToolTip deleteShortcuttoolTip;
deleteShortcuttoolTip.Content(box_value(GET_RESOURCE_STRING(IDS_DELETE_REMAPPING_BUTTON)));
ToolTipService::SetToolTip(deleteShortcut, deleteShortcuttoolTip);
parent.SetColumn(deleteShortcut, KeyboardManagerConstants::ShortcutTableRemoveColIndex);
parent.SetRow(deleteShortcut, parent.RowDefinitions().Size() - 1);
parent.Children().Append(deleteShortcut);

View File

@@ -176,8 +176,15 @@ void SingleKeyRemapControl::AddNewControlKeyRemapRow(Grid& parent, std::vector<s
// delete the SingleKeyRemapControl objects so that they get destructed
keyboardRemapControlObjects.erase(keyboardRemapControlObjects.begin() + bufferIndex);
});
// To set the accessible name of the delete button
deleteRemapKeys.SetValue(Automation::AutomationProperties::NameProperty(), box_value(GET_RESOURCE_STRING(IDS_DELETE_REMAPPING_BUTTON)));
// Add tooltip for delete button which would appear on hover
ToolTip deleteRemapKeystoolTip;
deleteRemapKeystoolTip.Content(box_value(GET_RESOURCE_STRING(IDS_DELETE_REMAPPING_BUTTON)));
ToolTipService::SetToolTip(deleteRemapKeys, deleteRemapKeystoolTip);
parent.SetColumn(deleteRemapKeys, KeyboardManagerConstants::RemapTableRemoveColIndex);
parent.SetRow(deleteRemapKeys, parent.RowDefinitions().Size() - 1);
parent.Children().Append(deleteRemapKeys);

View File

@@ -21,12 +21,11 @@ namespace Microsoft.Plugin.Folder.Sources.Result
public Wox.Plugin.Result Create(IPublicAPI contextApi)
{
var result = new Wox.Plugin.Result
var result = new Wox.Plugin.Result(StringMatcher.FuzzySearch(Search, Path.GetFileName(FilePath)).MatchData)
{
Title = Title,
SubTitle = string.Format(CultureInfo.CurrentCulture, Properties.Resources.wox_plugin_folder_select_file_result_subtitle, FilePath),
IcoPath = FilePath,
TitleHighlightData = StringMatcher.FuzzySearch(Search, Path.GetFileName(FilePath)).MatchData,
Action = c => ShellAction.Execute(FilePath, contextApi),
ContextData = new SearchResult { Type = ResultType.File, FullPath = FilePath },
};

View File

@@ -33,13 +33,12 @@ namespace Microsoft.Plugin.Folder.Sources.Result
public Wox.Plugin.Result Create(IPublicAPI contextApi)
{
return new Wox.Plugin.Result
return new Wox.Plugin.Result(StringMatcher.FuzzySearch(Search, Title).MatchData)
{
Title = Title,
IcoPath = Path,
SubTitle = string.Format(CultureInfo.CurrentCulture, Properties.Resources.wox_plugin_folder_select_folder_result_subtitle, Subtitle),
QueryTextDisplay = Path,
TitleHighlightData = StringMatcher.FuzzySearch(Search, Title).MatchData,
ContextData = new SearchResult { Type = ResultType.Folder, FullPath = Path },
Action = c => ShellAction.Execute(Path, contextApi),
};

View File

@@ -24,13 +24,12 @@ namespace Microsoft.Plugin.Folder
public Result Create(IPublicAPI contextApi)
{
return new Result
return new Result(StringMatcher.FuzzySearch(Search, Title).MatchData)
{
Title = Title,
IcoPath = Path,
SubTitle = string.Format(CultureInfo.CurrentCulture, Properties.Resources.wox_plugin_folder_select_folder_result_subtitle, Subtitle),
QueryTextDisplay = Path,
TitleHighlightData = StringMatcher.FuzzySearch(Search, Title).MatchData,
ContextData = new SearchResult { Type = ResultType.Folder, FullPath = Path },
Action = c => _shellAction.Execute(Path, contextApi),
};

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using Microsoft.Plugin.Program.ProgramArgumentParser;
using Mono.Collections.Generic;
using NUnit.Framework;
using Wox.Plugin;
@@ -34,7 +35,7 @@ namespace Microsoft.Plugin.Program.UnitTests.ProgramArgumentParser
// basic version of the Quey parser which can be found at Wox.Core.Plugin.QueryBuilder but did not want to create a project reference
var splittedSearchString = inputQuery?.Split(Query.TermSeparator, System.StringSplitOptions.RemoveEmptyEntries);
var cleanQuery = string.Join(Query.TermSeparator, splittedSearchString);
var query = new Query(cleanQuery, cleanQuery, splittedSearchString, string.Empty);
var query = new Query(cleanQuery, cleanQuery, new ReadOnlyCollection<string>(splittedSearchString), string.Empty);
// Act
string program = null, programArguments = null;

View File

@@ -19,9 +19,9 @@ namespace Microsoft.Plugin.Program
if (!string.IsNullOrEmpty(query?.Search))
{
// First Argument is always (part of) the program, 2nd term is possibly a Program Argument
if (query.Terms.Length > 1)
if (query.Terms.Count > 1)
{
for (var i = 1; i < query.Terms.Length; i++)
for (var i = 1; i < query.Terms.Count; i++)
{
if (!string.Equals(query.Terms[i], DoubleDash, StringComparison.Ordinal))
{

View File

@@ -19,9 +19,9 @@ namespace Microsoft.Plugin.Program
if (!string.IsNullOrEmpty(query?.Search))
{
// First Argument is always (part of) the program, 2nd term is possibly a Program Argument
if (query.Terms.Length > 1)
if (query.Terms.Count > 1)
{
for (var i = 1; i < query.Terms.Length; i++)
for (var i = 1; i < query.Terms.Count; i++)
{
if (!ArgumentPrefixRegex.IsMatch(query.Terms[i]))
{

View File

@@ -8,6 +8,7 @@ using System.Runtime.InteropServices.ComTypes;
using System.Text;
using Accessibility;
using Microsoft.Plugin.Program.Logger;
using Wox.Plugin.Logger;
namespace Microsoft.Plugin.Program.Programs
{
@@ -131,6 +132,7 @@ namespace Microsoft.Plugin.Program.Programs
public bool HasArguments { get; set; }
// Retrieve the target path using Shell Link
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "HRESULT E_FAIL is thrown while fetching description and E_FAIL does not relate to any specific exception.")]
public string RetrieveTargetPath(string path)
{
var link = new ShellLink();
@@ -160,8 +162,16 @@ namespace Microsoft.Plugin.Program.Programs
if (!string.IsNullOrEmpty(target))
{
buffer = new StringBuilder(MAX_PATH);
((IShellLinkW)link).GetDescription(buffer, MAX_PATH);
Description = buffer.ToString();
try
{
((IShellLinkW)link).GetDescription(buffer, MAX_PATH);
Description = buffer.ToString();
}
catch (Exception e)
{
Log.Exception($"|Failed to fetch description for {target}, {e.Message}", e, GetType());
Description = string.Empty;
}
StringBuilder argumentBuffer = new StringBuilder(MAX_PATH);
((IShellLinkW)link).GetArguments(argumentBuffer, argumentBuffer.Capacity);

View File

@@ -109,7 +109,7 @@ namespace Microsoft.Plugin.Program.Programs
// To set the title to always be the displayname of the packaged application
result.Title = DisplayName;
result.TitleHighlightData = StringMatcher.FuzzySearch(query, Name).MatchData;
result.SetTitleHighlightData(StringMatcher.FuzzySearch(query, Name).MatchData);
var toolTipTitle = string.Format(CultureInfo.CurrentCulture, "{0}: {1}", Properties.Resources.powertoys_run_plugin_program_file_name, result.Title);
var toolTipText = string.Format(CultureInfo.CurrentCulture, "{0}: {1}", Properties.Resources.powertoys_run_plugin_program_file_path, Package.Location);

View File

@@ -233,7 +233,7 @@ namespace Microsoft.Plugin.Program.Programs
// To set the title for the result to always be the name of the application
result.Title = Name;
result.TitleHighlightData = StringMatcher.FuzzySearch(query, Name).MatchData;
result.SetTitleHighlightData(StringMatcher.FuzzySearch(query, Name).MatchData);
var toolTipTitle = string.Format(CultureInfo.CurrentCulture, "{0}: {1}", Properties.Resources.powertoys_run_plugin_program_file_name, result.Title);
var toolTipText = string.Format(CultureInfo.CurrentCulture, "{0}: {1}", Properties.Resources.powertoys_run_plugin_program_file_path, FullPath);

View File

@@ -32,5 +32,10 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers">
<Version>3.3.0</Version>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@@ -40,7 +40,7 @@ namespace Microsoft.Plugin.Uri.UnitTests.UriHelper
[TestCase("[::]", true, "http://[::]/")]
[TestCase("[2001:0DB8::1]", true, "http://[2001:db8::1]/")]
[TestCase("[2001:0DB8::1]:80", true, "http://[2001:db8::1]/")]
public void TryParse_CanParseHostName(string query, bool expectedSuccess, string expectedResult)
public void TryParseCanParseHostName(string query, bool expectedSuccess, string expectedResult)
{
// Arrange
var parser = new ExtendedUriParser();

View File

@@ -78,10 +78,10 @@ namespace Wox.Core.Plugin
metadata.PluginDirectory = pluginDirectory;
// for plugins which doesn't has ActionKeywords key
metadata.ActionKeywords = metadata.ActionKeywords ?? new List<string> { metadata.ActionKeyword };
metadata.SetActionKeywords(metadata.GetActionKeywords() ?? new List<string> { metadata.ActionKeyword });
// for plugin still use old ActionKeyword
metadata.ActionKeyword = metadata.ActionKeywords?[0];
metadata.ActionKeyword = metadata.GetActionKeywords()?[0];
}
catch (Exception e)
{

View File

@@ -125,7 +125,7 @@ namespace Wox.Core.Plugin
}
// Plugins may have multiple ActionKeywords, eg. WebSearch
plugin.Metadata.ActionKeywords.Where(x => x != Query.GlobalPluginWildcardSign)
plugin.Metadata.GetActionKeywords().Where(x => x != Query.GlobalPluginWildcardSign)
.ToList()
.ForEach(x => NonGlobalPlugins[x] = plugin);
}
@@ -244,7 +244,7 @@ namespace Wox.Core.Plugin
private static bool IsGlobalPlugin(PluginMetadata metadata)
{
return metadata.ActionKeywords.Contains(Query.GlobalPluginWildcardSign);
return metadata.GetActionKeywords().Contains(Query.GlobalPluginWildcardSign);
}
/// <summary>
@@ -258,7 +258,6 @@ namespace Wox.Core.Plugin
}
public static IEnumerable<PluginPair> GetPluginsForInterface<T>()
where T : IFeatures
{
return AllPlugins.Where(p => p.Plugin is T);
}
@@ -320,7 +319,7 @@ namespace Wox.Core.Plugin
NonGlobalPlugins[newActionKeyword] = plugin;
}
plugin.Metadata.ActionKeywords.Add(newActionKeyword);
plugin.Metadata.GetActionKeywords().Add(newActionKeyword);
}
/// <summary>
@@ -332,7 +331,7 @@ namespace Wox.Core.Plugin
var plugin = GetPluginForId(id);
if (oldActionkeyword == Query.GlobalPluginWildcardSign
&& // Plugins may have multiple ActionKeywords that are global, eg. WebSearch
plugin.Metadata.ActionKeywords
plugin.Metadata.GetActionKeywords()
.Where(x => x == Query.GlobalPluginWildcardSign)
.ToList()
.Count == 1)
@@ -345,7 +344,7 @@ namespace Wox.Core.Plugin
NonGlobalPlugins.Remove(oldActionkeyword);
}
plugin.Metadata.ActionKeywords.Remove(oldActionkeyword);
plugin.Metadata.GetActionKeywords().Remove(oldActionkeyword);
}
public static void ReplaceActionKeyword(string id, string oldActionKeyword, string newActionKeyword)

View File

@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Mono.Collections.Generic;
using Wox.Plugin;
namespace Wox.Core.Plugin
@@ -63,7 +64,7 @@ namespace Wox.Core.Plugin
}
// A new query is constructed for each plugin as they have different action keywords
var query = new Query(rawQuery, search, terms, pluginActionKeyword);
var query = new Query(rawQuery, search, new ReadOnlyCollection<string>(terms), pluginActionKeyword);
pluginQueryPair.TryAdd(pluginPair, query);
}
@@ -80,7 +81,7 @@ namespace Wox.Core.Plugin
{
if (!pluginQueryPair.ContainsKey(globalPlugin))
{
var query = new Query(rawQuery, rawQuery, terms, string.Empty);
var query = new Query(rawQuery, rawQuery, new ReadOnlyCollection<string>(terms), string.Empty);
pluginQueryPair.Add(globalPlugin, query);
}
}

View File

@@ -20,7 +20,7 @@ namespace Wox.Infrastructure.UserSettings
var settings = Plugins[metadata.ID];
if (settings.ActionKeywords?.Count > 0)
{
metadata.ActionKeywords = settings.ActionKeywords;
metadata.SetActionKeywords(settings.ActionKeywords);
metadata.ActionKeyword = settings.ActionKeywords[0];
}
@@ -32,7 +32,7 @@ namespace Wox.Infrastructure.UserSettings
{
ID = metadata.ID,
Name = metadata.Name,
ActionKeywords = metadata.ActionKeywords,
ActionKeywords = metadata.GetActionKeywords(),
Disabled = metadata.Disabled,
};
}

View File

@@ -2,6 +2,9 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
namespace Wox.Plugin
{
public static class AllowedLanguage
@@ -18,8 +21,14 @@ namespace Wox.Plugin
public static bool IsAllowed(string language)
{
return language.ToUpper() == CSharp.ToUpper()
|| language.ToUpper() == Executable.ToUpper();
if (language == null)
{
throw new ArgumentNullException(nameof(language));
}
// Using InvariantCulture since this is a command line arg
return language.ToUpper(CultureInfo.InvariantCulture) == CSharp.ToUpper(CultureInfo.InvariantCulture)
|| language.ToUpper(CultureInfo.InvariantCulture) == Executable.ToUpper(CultureInfo.InvariantCulture);
}
}
}

View File

@@ -6,11 +6,7 @@ using System.Collections.Generic;
namespace Wox.Plugin
{
public interface IFeatures
{
}
public interface IContextMenu : IFeatures
public interface IContextMenu
{
List<ContextMenuResult> LoadContextMenus(Result selectedResult);
}
@@ -18,14 +14,14 @@ namespace Wox.Plugin
/// <summary>
/// Represent plugins that support internationalization
/// </summary>
public interface IPluginI18n : IFeatures
public interface IPluginI18n
{
string GetTranslatedPluginTitle();
string GetTranslatedPluginDescription();
}
public interface IResultUpdated : IFeatures
public interface IResultUpdated
{
event ResultUpdatedEventHandler ResultsUpdated;
}

View File

@@ -6,7 +6,7 @@ using System.Collections.Generic;
namespace Wox.Plugin
{
public interface IDelayedExecutionPlugin : IFeatures
public interface IDelayedExecutionPlugin
{
List<Result> Query(Query query, bool delayedExecution);
}

View File

@@ -0,0 +1,14 @@
{
"Projects": [
{
"LanguageSet": "Azure_Languages",
"LocItems": [
{
"SourceFile": "src\\modules\\launcher\\Wox.Plugin\\Properties\\Resources.resx",
"CopyOption": "LangIDOnName",
"OutputPath": "src\\modules\\launcher\\Wox.Plugin\\Properties"
}
]
}
]
}

View File

@@ -14,6 +14,13 @@ namespace Wox.Plugin
{
private string _pluginDirectory;
private List<string> _actionKeywords;
public PluginMetadata(List<string> actionKeywords = null)
{
_actionKeywords = actionKeywords;
}
public string ID { get; set; }
public string Name { get; set; }
@@ -51,7 +58,15 @@ namespace Wox.Plugin
public string ActionKeyword { get; set; }
public List<string> ActionKeywords { get; set; }
public List<string> GetActionKeywords()
{
return _actionKeywords;
}
public void SetActionKeywords(List<string> value)
{
_actionKeywords = value;
}
public string IcoPath { get; set; }

View File

@@ -2,6 +2,8 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace Wox.Plugin
{
public class PluginPair
@@ -19,7 +21,8 @@ namespace Wox.Plugin
{
if (obj is PluginPair r)
{
return string.Equals(r.Metadata.ID, Metadata.ID);
// Using Ordinal since this is used internally
return string.Equals(r.Metadata.ID, Metadata.ID, StringComparison.Ordinal);
}
else
{
@@ -29,7 +32,8 @@ namespace Wox.Plugin
public override int GetHashCode()
{
var hashcode = Metadata.ID?.GetHashCode() ?? 0;
// Using Ordinal since this is used internally
var hashcode = Metadata.ID?.GetHashCode(StringComparison.Ordinal) ?? 0;
return hashcode;
}
}

View File

@@ -0,0 +1,90 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Wox.Plugin.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Wox.Plugin.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Copying path {0} has failed, it will now be deleted for consistency.
/// </summary>
public static string filesfolder_copy_failed {
get {
return ResourceManager.GetString("filesfolder_copy_failed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Not able to delete folder {0}, please go to the location and manually delete it.
/// </summary>
public static string filesfolder_removefolder_failed {
get {
return ResourceManager.GetString("filesfolder_removefolder_failed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unable to verify folders and files between {0} and {1}.
/// </summary>
public static string filesfolder_verifybothfolderfilesequal_failed {
get {
return ResourceManager.GetString("filesfolder_verifybothfolderfilesequal_failed", resourceCulture);
}
}
}
}

View File

@@ -0,0 +1,132 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="filesfolder_copy_failed" xml:space="preserve">
<value>Copying path {0} has failed, it will now be deleted for consistency</value>
<comment>parameter: targetPath</comment>
</data>
<data name="filesfolder_removefolder_failed" xml:space="preserve">
<value>Not able to delete folder {0}, please go to the location and manually delete it</value>
<comment>parameter: path</comment>
</data>
<data name="filesfolder_verifybothfolderfilesequal_failed" xml:space="preserve">
<value>Unable to verify folders and files between {0} and {1}</value>
<comment>paramaters: fromPath, toPath</comment>
</data>
</root>

View File

@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Mono.Collections.Generic;
namespace Wox.Plugin
{
@@ -18,7 +19,7 @@ namespace Wox.Plugin
/// Initializes a new instance of the <see cref="Query"/> class.
/// to allow unit tests for plug ins
/// </summary>
public Query(string rawQuery, string search, string[] terms, string actionKeyword = "")
public Query(string rawQuery, string search, ReadOnlyCollection<string> terms, string actionKeyword = "")
{
Search = search;
RawQuery = rawQuery;
@@ -41,9 +42,9 @@ namespace Wox.Plugin
public string Search { get; internal set; }
/// <summary>
/// Gets or sets the raw query splited into a string array.
/// Gets the raw query splited into a string array.
/// </summary>
public string[] Terms { get; set; }
public ReadOnlyCollection<string> Terms { get; private set; }
/// <summary>
/// Query can be splited into multiple terms by whitespace

View File

@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Windows;
using System.Windows.Media;
@@ -16,6 +17,7 @@ namespace Wox.Plugin
private ToolTipData _toolTipData;
private string _pluginDirectory;
private string _icoPath;
private IList<int> _titleHighlightData;
public string Title
{
@@ -26,7 +28,13 @@ namespace Wox.Plugin
set
{
_title = value.Replace("\n", " ");
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
// Using Ordinal since this is used internally
_title = value.Replace("\n", " ", StringComparison.Ordinal);
}
}
@@ -90,15 +98,32 @@ namespace Wox.Plugin
public int Score { get; set; }
/// <summary>
/// Gets or sets a list of indexes for the characters to be highlighted in Title
/// </summary>
public IList<int> TitleHighlightData { get; set; }
public Result(IList<int> titleHighlightData = null, IList<int> subTitleHighlightData = null)
{
_titleHighlightData = titleHighlightData;
SubTitleHighlightData = subTitleHighlightData;
}
/// <summary>
/// Gets or sets a list of indexes for the characters to be highlighted in SubTitle
/// Gets a list of indexes for the characters to be highlighted in Title
/// </summary>
public IList<int> SubTitleHighlightData { get; set; }
public IList<int> GetTitleHighlightData()
{
return _titleHighlightData;
}
/// <summary>
/// Sets a list of indexes for the characters to be highlighted in Title
/// </summary>
public void SetTitleHighlightData(IList<int> value)
{
_titleHighlightData = value;
}
/// <summary>
/// Gets a list of indexes for the characters to be highlighted in SubTitle
/// </summary>
public IList<int> SubTitleHighlightData { get; private set; }
/// <summary>
/// Gets or sets only results that originQuery match with current query will be displayed in the panel
@@ -129,10 +154,11 @@ namespace Wox.Plugin
{
var r = obj as Result;
var equality = string.Equals(r?.Title, Title) &&
string.Equals(r?.SubTitle, SubTitle) &&
string.Equals(r?.IcoPath, IcoPath) &&
TitleHighlightData == r.TitleHighlightData &&
// Using Ordinal since this is used internally
var equality = string.Equals(r?.Title, Title, StringComparison.Ordinal) &&
string.Equals(r?.SubTitle, SubTitle, StringComparison.Ordinal) &&
string.Equals(r?.IcoPath, IcoPath, StringComparison.Ordinal) &&
GetTitleHighlightData() == r.GetTitleHighlightData() &&
SubTitleHighlightData == r.SubTitleHighlightData;
return equality;
@@ -140,18 +166,16 @@ namespace Wox.Plugin
public override int GetHashCode()
{
var hashcode = (Title?.GetHashCode() ?? 0) ^
(SubTitle?.GetHashCode() ?? 0);
// Using Ordinal since this is used internally
var hashcode = (Title?.GetHashCode(StringComparison.Ordinal) ?? 0) ^
(SubTitle?.GetHashCode(StringComparison.Ordinal) ?? 0);
return hashcode;
}
public override string ToString()
{
return string.Format("{0} : {1}", Title, SubTitle);
}
public Result()
{
// Using CurrentCulture since this is user facing
return string.Format(CultureInfo.CurrentCulture, "{0} : {1}", Title, SubTitle);
}
/// <summary>

View File

@@ -9,7 +9,12 @@ namespace Wox.Plugin
{
public class ResultUpdatedEventArgs : EventArgs
{
public List<Result> Results { get; set; }
public List<Result> Results { get; private set; }
public ResultUpdatedEventArgs(List<Result> results = null)
{
Results = results;
}
public Query Query { get; set; }
}

View File

@@ -3,9 +3,11 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using System.IO;
using System.Reflection;
using Wox.Plugin.Logger;
using Wox.Plugin.Properties;
namespace Wox.Plugin.SharedCommands
{
@@ -56,9 +58,10 @@ namespace Wox.Plugin.SharedCommands
string error = $"Copying path {targetPath} has failed";
Log.Exception(error, e, MethodBase.GetCurrentMethod().DeclaringType);
#if DEBUG
throw e;
throw;
#else
System.Windows.MessageBox.Show(string.Format("Copying path {0} has failed, it will now be deleted for consistency", targetPath));
// Using CurrentCulture since this is user facing
System.Windows.MessageBox.Show(string.Format(CultureInfo.CurrentCulture, Resources.filesfolder_copy_failed, targetPath));
RemoveFolder(targetPath);
#endif
}
@@ -91,9 +94,10 @@ namespace Wox.Plugin.SharedCommands
string error = $"Unable to verify folders and files between {fromPath} and {toPath}";
Log.Exception(error, e, MethodBase.GetCurrentMethod().DeclaringType);
#if DEBUG
throw e;
throw;
#else
System.Windows.MessageBox.Show(string.Format(error));
// Using CurrentCulture since this is user facing
System.Windows.MessageBox.Show(string.Format(CultureInfo.CurrentCulture, Resources.filesfolder_verifybothfolderfilesequal_failed, fromPath, toPath));
return false;
#endif
}
@@ -113,12 +117,13 @@ namespace Wox.Plugin.SharedCommands
catch (Exception e)
#pragma warning restore CS0168 // Variable is declared but never used
{
string error = $"Not able to delete folder {path}, please go to the location and manually delete it";
string error = $"Not able to delete folder {path}";
Log.Exception(error, e, MethodBase.GetCurrentMethod().DeclaringType);
#if DEBUG
throw e;
throw;
#else
System.Windows.MessageBox.Show(string.Format(error));
// Using CurrentCulture since this is user facing
System.Windows.MessageBox.Show(string.Format(CultureInfo.CurrentCulture, Resources.filesfolder_removefolder_failed, path));
#endif
}
}

View File

@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.InteropServices;
using System.Text;
using static Wox.Plugin.SharedCommands.ShellCommand;
namespace Wox.Plugin.SharedCommands
{
internal static class NativeMethods
{
[DllImport("user32.dll")]
public static extern bool EnumThreadWindows(uint threadId, EnumThreadDelegate lpfn, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int GetWindowText(IntPtr hwnd, StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll")]
public static extern int GetWindowTextLength(IntPtr hwnd);
}
}

View File

@@ -15,8 +15,13 @@ namespace Wox.Plugin.SharedCommands
/// Opens search in a new browser. If no browser path is passed in then Chrome is used.
/// Leave browser path blank to use Chrome.
/// </summary>
public static void NewBrowserWindow(this string url, string browserPath)
public static void NewBrowserWindow(this Uri url, string browserPath)
{
if (url == null)
{
throw new ArgumentNullException(nameof(url));
}
var browserExecutableName = browserPath?
.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None)
.Last();
@@ -24,7 +29,7 @@ namespace Wox.Plugin.SharedCommands
var browser = string.IsNullOrEmpty(browserExecutableName) ? "chrome" : browserPath;
// Internet Explorer will open url in new browser window, and does not take the --new-window parameter
var browserArguments = browserExecutableName == "iexplore.exe" ? url : "--new-window " + url;
var browserArguments = browserExecutableName == "iexplore.exe" ? url.AbsoluteUri : "--new-window " + url.AbsoluteUri;
try
{
@@ -34,7 +39,7 @@ namespace Wox.Plugin.SharedCommands
{
var psi = new ProcessStartInfo
{
FileName = url,
FileName = url.AbsoluteUri,
UseShellExecute = true,
};
Process.Start(psi);
@@ -44,24 +49,29 @@ namespace Wox.Plugin.SharedCommands
/// <summary>
/// Opens search as a tab in the default browser chosen in Windows settings.
/// </summary>
public static void NewTabInBrowser(this string url, string browserPath)
public static void NewTabInBrowser(this Uri url, string browserPath)
{
if (url == null)
{
throw new ArgumentNullException(nameof(url));
}
try
{
if (!string.IsNullOrEmpty(browserPath))
{
Process.Start(browserPath, url);
Process.Start(browserPath, url.AbsoluteUri);
}
else
{
Process.Start(url);
Process.Start(url.AbsoluteUri);
}
}
// This error may be thrown for Process.Start(browserPath, url)
catch (System.ComponentModel.Win32Exception)
{
Process.Start(url);
Process.Start(url.AbsoluteUri);
}
}
}

View File

@@ -4,7 +4,6 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
@@ -14,19 +13,15 @@ namespace Wox.Plugin.SharedCommands
{
public delegate bool EnumThreadDelegate(IntPtr hwnd, IntPtr lParam);
[DllImport("user32.dll")]
private static extern bool EnumThreadWindows(uint threadId, EnumThreadDelegate lpfn, IntPtr lParam);
[DllImport("user32.dll")]
private static extern int GetWindowText(IntPtr hwnd, StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll")]
private static extern int GetWindowTextLength(IntPtr hwnd);
private static bool containsSecurityWindow;
public static Process RunAsDifferentUser(ProcessStartInfo processStartInfo)
{
if (processStartInfo == null)
{
throw new ArgumentNullException(nameof(processStartInfo));
}
processStartInfo.Verb = "RunAsUser";
var process = Process.Start(processStartInfo);
@@ -55,7 +50,7 @@ namespace Wox.Plugin.SharedCommands
ProcessThreadCollection ptc = Process.GetCurrentProcess().Threads;
for (int i = 0; i < ptc.Count; i++)
{
EnumThreadWindows((uint)ptc[i].Id, CheckSecurityThread, IntPtr.Zero);
NativeMethods.EnumThreadWindows((uint)ptc[i].Id, CheckSecurityThread, IntPtr.Zero);
}
}
@@ -71,8 +66,8 @@ namespace Wox.Plugin.SharedCommands
private static string GetWindowTitle(IntPtr hwnd)
{
StringBuilder sb = new StringBuilder(GetWindowTextLength(hwnd) + 1);
GetWindowText(hwnd, sb, sb.Capacity);
StringBuilder sb = new StringBuilder(NativeMethods.GetWindowTextLength(hwnd) + 1);
_ = NativeMethods.GetWindowText(hwnd, sb, sb.Capacity);
return sb.ToString();
}

View File

@@ -22,7 +22,7 @@ namespace Wox.Plugin
private const string HighContrastWhiteTheme = "HighContrast.Accent5";
private Theme currentTheme;
private bool _disposed = false;
private bool _disposed;
public event ThemeChangedHandler ThemeChanged;

View File

@@ -16,7 +16,7 @@ namespace Wox.Plugin
{
if (string.IsNullOrEmpty(title))
{
throw new ArgumentException("title cannot be null or empty", "title");
throw new ArgumentException("title cannot be null or empty", nameof(title));
}
Title = title;

View File

@@ -13,6 +13,7 @@
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<Platforms>x64</Platforms>
<NeutralLanguage>en-US</NeutralLanguage>
</PropertyGroup>
@@ -68,6 +69,10 @@
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" />
<PackageReference Include="MahApps.Metro" Version="2.3.0" />
<PackageReference Include="ControlzEx" Version="4.3.2" />
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Mono.Cecil" Version="0.11.2" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NLog.Extensions.Logging" Version="1.6.5" />
@@ -96,4 +101,17 @@
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using Mono.Collections.Generic;
using NUnit.Framework;
using Wox.Core.Plugin;
using Wox.Plugin;
@@ -26,7 +27,7 @@ namespace Wox.Test
// Arrange
var nonGlobalPlugins = new Dictionary<string, PluginPair>
{
{ ">", new PluginPair { Metadata = new PluginMetadata { ActionKeywords = new List<string> { ">" } } } },
{ ">", new PluginPair { Metadata = new PluginMetadata(new List<string> { ">" } ) } },
};
string searchQuery = "> file.txt file2 file3";
@@ -43,7 +44,7 @@ namespace Wox.Test
// Arrange
var nonGlobalPlugins = new Dictionary<string, PluginPair>
{
{ ">", new PluginPair { Metadata = new PluginMetadata { ActionKeywords = new List<string> { ">" }, Disabled = true } } },
{ ">", new PluginPair { Metadata = new PluginMetadata(new List<string> { ">" }) { Disabled = true } } },
};
string searchQuery = "> file.txt file2 file3";
@@ -72,7 +73,7 @@ namespace Wox.Test
{
// Arrange
string searchQuery = "> query";
var firstPlugin = new PluginPair { Metadata = new PluginMetadata { ActionKeywords = new List<string> { ">" } } };
var firstPlugin = new PluginPair { Metadata = new PluginMetadata(new List<string> { ">" } ) };
var secondPlugin = new PluginPair { Metadata = new PluginMetadata { ActionKeyword = ">" } };
var nonGlobalPluginWithActionKeywords = new Dictionary<string, PluginPair>
@@ -85,7 +86,7 @@ namespace Wox.Test
{ ">", secondPlugin },
};
string[] terms = { ">", "query" };
Query expectedQuery = new Query("> query", "query", terms, ">");
Query expectedQuery = new Query("> query", "query", new ReadOnlyCollection<string>(terms), ">");
// Act
var queriesForPluginsWithActionKeywords = QueryBuilder.Build(ref searchQuery, nonGlobalPluginWithActionKeywords);
@@ -103,7 +104,7 @@ namespace Wox.Test
public void QueryBuilderShouldGenerateCorrectQueriesForPluginsWithMultipleActionKeywords()
{
// Arrange
var plugin = new PluginPair { Metadata = new PluginMetadata { ActionKeywords = new List<string> { "a", "b" } } };
var plugin = new PluginPair { Metadata = new PluginMetadata(new List<string> { "a", "b" } ) };
var nonGlobalPlugins = new Dictionary<string, PluginPair>
{
{ "a", plugin },
@@ -129,7 +130,7 @@ namespace Wox.Test
public void QueryBuildShouldGenerateSameSearchQueryWithOrWithoutSpaceAfterActionKeyword()
{
// Arrange
var plugin = new PluginPair { Metadata = new PluginMetadata { ActionKeywords = new List<string> { "a" } } };
var plugin = new PluginPair { Metadata = new PluginMetadata(new List<string> { "a" } ) };
var nonGlobalPlugins = new Dictionary<string, PluginPair>
{
{ "a", plugin },
@@ -198,8 +199,8 @@ namespace Wox.Test
// Assert
// Using Ordinal since this is used internally
Assert.IsTrue(firstQuery.Terms[0].Equals("cd", StringComparison.Ordinal) && firstQuery.Terms[1].Equals("efgh", StringComparison.Ordinal) && firstQuery.Terms.Length == 2);
Assert.IsTrue(secondQuery.Terms[0].Equals("efgh", StringComparison.Ordinal) && secondQuery.Terms.Length == 1);
Assert.IsTrue(firstQuery.Terms[0].Equals("cd", StringComparison.Ordinal) && firstQuery.Terms[1].Equals("efgh", StringComparison.Ordinal) && firstQuery.Terms.Count == 2);
Assert.IsTrue(secondQuery.Terms[0].Equals("efgh", StringComparison.Ordinal) && secondQuery.Terms.Count == 1);
}
}
}

View File

@@ -40,8 +40,8 @@
<Item ItemId=";FileExplorer_Admin_Restart_Warning_Description" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Some of the File Explorer modules could not be turned off/on as per your settings. Please restart PowerToys as admin for the changes to take place.]]></Val>
<Tgt Cat="Text" Stat="Update" Orig="New">
<Val><![CDATA[Einige der Datei-Explorer-Module konnten gemäß Ihren Einstellungen nicht registriert bzw. aus der Registrierung entfernt werden. Starten Sie PowerToys als Administrator neu, damit die Änderungen ausgeführt werden können.]]></Val>
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[Einige der Datei-Explorer-Module konnten gemäß Ihren Einstellungen nicht aktiviert bzw. deaktiviert werden. Starten Sie PowerToys als Administrator neu, damit die Änderungen ausgeführt werden können.]]></Val>
</Tgt>
<Prev Cat="Text">
<Val><![CDATA[Some of the File Explorer modules could not be registered/unregistered as per your settings. Please restart PowerToys as admin for the changes to take place.]]></Val>
@@ -70,8 +70,8 @@
<Item ItemId=";FileExplorer_Admin_Restart_Warning_Title" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Couldn't modify File Explorer modules]]></Val>
<Tgt Cat="Text" Stat="Update" Orig="New">
<Val><![CDATA[Fehler beim Ändern der Datei-Explorer-Module.]]></Val>
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[Fehler beim Ändern von Datei-Explorer-Modulen]]></Val>
</Tgt>
<Prev Cat="Text">
<Val><![CDATA[Failed to modify File Explorer modules]]></Val>

View File

@@ -40,8 +40,8 @@
<Item ItemId=";FileExplorer_Admin_Restart_Warning_Description" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Some of the File Explorer modules could not be turned off/on as per your settings. Please restart PowerToys as admin for the changes to take place.]]></Val>
<Tgt Cat="Text" Stat="Update" Orig="New">
<Val><![CDATA[No se pudieron registrar algunos módulos del Explorador de archivos, o no se pudo anular su registro, según la configuración. Reinicie PowerToys como administrador para que los cambios se apliquen.]]></Val>
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[No se pudieron activar o desactivar algunos módulos del Explorador de archivos según la configuración. Reinicie PowerToys como administrador para que los cambios se apliquen.]]></Val>
</Tgt>
<Prev Cat="Text">
<Val><![CDATA[Some of the File Explorer modules could not be registered/unregistered as per your settings. Please restart PowerToys as admin for the changes to take place.]]></Val>
@@ -70,7 +70,7 @@
<Item ItemId=";FileExplorer_Admin_Restart_Warning_Title" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Couldn't modify File Explorer modules]]></Val>
<Tgt Cat="Text" Stat="Update" Orig="New">
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[No se pudieron modificar los módulos del Explorador de archivos]]></Val>
</Tgt>
<Prev Cat="Text">

View File

@@ -40,8 +40,8 @@
<Item ItemId=";FileExplorer_Admin_Restart_Warning_Description" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Some of the File Explorer modules could not be turned off/on as per your settings. Please restart PowerToys as admin for the changes to take place.]]></Val>
<Tgt Cat="Text" Stat="Update" Orig="New">
<Val><![CDATA[Impossible d'inscrire/de désinscrire certains modules de l'Explorateur de fichiers selon vos paramètres. Redémarrez PowerToys comme administrateur pour appliquer les changements.]]></Val>
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[Impossible de désactiver/d'activer certains modules de l'Explorateur de fichiers selon vos paramètres. Redémarrez PowerToys comme administrateur pour appliquer les changements.]]></Val>
</Tgt>
<Prev Cat="Text">
<Val><![CDATA[Some of the File Explorer modules could not be registered/unregistered as per your settings. Please restart PowerToys as admin for the changes to take place.]]></Val>
@@ -70,8 +70,8 @@
<Item ItemId=";FileExplorer_Admin_Restart_Warning_Title" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Couldn't modify File Explorer modules]]></Val>
<Tgt Cat="Text" Stat="Update" Orig="New">
<Val><![CDATA[La modification des modules de l'Explorateur de fichiers a échoué]]></Val>
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[Impossible de modifier les modules de l'Explorateur de fichiers]]></Val>
</Tgt>
<Prev Cat="Text">
<Val><![CDATA[Failed to modify File Explorer modules]]></Val>

View File

@@ -40,8 +40,8 @@
<Item ItemId=";FileExplorer_Admin_Restart_Warning_Description" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Some of the File Explorer modules could not be turned off/on as per your settings. Please restart PowerToys as admin for the changes to take place.]]></Val>
<Tgt Cat="Text" Stat="Update" Orig="New">
<Val><![CDATA[Een aantal modules van Bestandenverkenner kunnen niet worden geregistreerd/uitgeschreven vanwege uw instellingen. Start PowerToys opnieuw als beheerder om de wijzigingen door te voeren.]]></Val>
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[Een aantal modules van Bestandenverkenner kunnen niet worden in-/uitgeschakeld vanwege uw instellingen. Start PowerToys opnieuw als beheerder om de wijzigingen door te voeren.]]></Val>
</Tgt>
<Prev Cat="Text">
<Val><![CDATA[Some of the File Explorer modules could not be registered/unregistered as per your settings. Please restart PowerToys as admin for the changes to take place.]]></Val>
@@ -70,7 +70,7 @@
<Item ItemId=";FileExplorer_Admin_Restart_Warning_Title" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Couldn't modify File Explorer modules]]></Val>
<Tgt Cat="Text" Stat="Update" Orig="New">
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[Kan de modules van Bestandenverkenner niet wijzigen]]></Val>
</Tgt>
<Prev Cat="Text">

View File

@@ -40,8 +40,8 @@
<Item ItemId=";FileExplorer_Admin_Restart_Warning_Description" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Some of the File Explorer modules could not be turned off/on as per your settings. Please restart PowerToys as admin for the changes to take place.]]></Val>
<Tgt Cat="Text" Stat="Update" Orig="New">
<Val><![CDATA[Não foi possível registrar/cancelar o registro de alguns dos módulos do Explorador de Arquivos de acordo com as configurações. Reinicie os PowerToys como administrador para que as alterações entrem em vigor.]]></Val>
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[Não foi possível ativar/desativar alguns dos módulos do Explorador de Arquivos de acordo com as configurações. Reinicie o PowerToys como administrador para que as alterações entrem em vigor.]]></Val>
</Tgt>
<Prev Cat="Text">
<Val><![CDATA[Some of the File Explorer modules could not be registered/unregistered as per your settings. Please restart PowerToys as admin for the changes to take place.]]></Val>
@@ -70,7 +70,7 @@
<Item ItemId=";FileExplorer_Admin_Restart_Warning_Title" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Couldn't modify File Explorer modules]]></Val>
<Tgt Cat="Text" Stat="Update" Orig="New">
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[Falha ao modificar os módulos do Explorador de Arquivos]]></Val>
</Tgt>
<Prev Cat="Text">

View File

@@ -40,8 +40,8 @@
<Item ItemId=";FileExplorer_Admin_Restart_Warning_Description" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Some of the File Explorer modules could not be turned off/on as per your settings. Please restart PowerToys as admin for the changes to take place.]]></Val>
<Tgt Cat="Text" Stat="Update" Orig="New">
<Val><![CDATA[Bazı Dosya Gezgini modülleri, ayarlarınıza göre kaydedilemedi/kaydı silinemedi. Değişikliklerin geçerli olması için lütfen PowerToys'u yönetici olarak yeniden başlatın.]]></Val>
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[Bazı Dosya Gezgini modülleri, ayarlarınıza göre devre dışı bırakılamadı/etkinleştirilemedi. Değişikliklerin geçerli olması için lütfen PowerToys'u yönetici olarak yeniden başlatın.]]></Val>
</Tgt>
<Prev Cat="Text">
<Val><![CDATA[Some of the File Explorer modules could not be registered/unregistered as per your settings. Please restart PowerToys as admin for the changes to take place.]]></Val>
@@ -70,7 +70,7 @@
<Item ItemId=";FileExplorer_Admin_Restart_Warning_Title" ItemType="0;.resx" PsrId="211" Leaf="true">
<Str Cat="Text">
<Val><![CDATA[Couldn't modify File Explorer modules]]></Val>
<Tgt Cat="Text" Stat="Update" Orig="New">
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[Dosya Gezgini modülleri değiştirilemedi]]></Val>
</Tgt>
<Prev Cat="Text">