Merge branch 'master' into users/niels9001/settings-incorrecttheminglabels

This commit is contained in:
Niels Laute
2020-10-27 12:11:16 +01:00
committed by GitHub
61 changed files with 2506 additions and 1143 deletions

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

@@ -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

@@ -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

@@ -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

@@ -404,15 +404,15 @@ namespace FancyZonesEditor
_gridModel.ColumnPercents.Add(((_multiplier * (col + 1)) / cols) - ((_multiplier * col) / cols));
}
int index = ZoneCount - 1;
for (int col = cols - 1; col >= 0; col--)
int index = 0;
for (int row = 0; row < rows; row++)
{
for (int row = rows - 1; row >= 0; row--)
for (int col = 0; col < cols; col++)
{
_gridModel.CellChildMap[row, col] = index--;
if (index < 0)
_gridModel.CellChildMap[row, col] = index++;
if (index == ZoneCount)
{
index = 0;
index--;
}
}
}

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,22 +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)
{
auto zones = zoneSet->GetZones();
for (size_t i = 0; i < zones.size(); i++)
auto zoneSet = workArea->ActiveZoneSet();
if (zoneSet)
{
const auto& zone = zones[i];
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(i, workArea);
zoneRects.emplace_back(zoneRect);
zoneRectsInfo.emplace_back(zoneId, workArea);
}
}
}
}
@@ -1225,22 +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)
{
auto zones = zoneSet->GetZones();
for (size_t i = 0; i < zones.size(); i++)
auto zoneSet = workArea->ActiveZoneSet();
if (zoneSet)
{
const auto& zone = zones[i];
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(i, workArea);
zoneRects.emplace_back(zoneRect);
zoneRectsInfo.emplace_back(zoneId, workArea);
}
}
}
}
@@ -1353,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

@@ -135,7 +135,7 @@ RECT Zone::ComputeActualZoneRect(HWND window, HWND zoneWindow) const noexcept
winrt::com_ptr<IZone> MakeZone(const RECT& zoneRect, const size_t zoneId) noexcept
{
if (ValidateZoneRect(zoneRect) && zoneId > 0)
if (ValidateZoneRect(zoneRect) && zoneId >= 0)
{
return winrt::make_self<Zone>(zoneRect, zoneId);
}

View File

@@ -10,6 +10,8 @@
#include <common/dpi_aware.h>
#include <limits>
#include <map>
#include <utility>
using namespace FancyZonesUtils;
@@ -113,7 +115,7 @@ public:
{
}
ZoneSet(ZoneSetConfig const& config, std::vector<winrt::com_ptr<IZone>> zones) :
ZoneSet(ZoneSetConfig const& config, ZonesMap zones) :
m_config(config),
m_zones(zones)
{
@@ -128,8 +130,8 @@ public:
ZonesFromPoint(POINT pt) const noexcept;
IFACEMETHODIMP_(std::vector<size_t>)
GetZoneIndexSetFromWindow(HWND window) const noexcept;
IFACEMETHODIMP_(std::vector<winrt::com_ptr<IZone>>)
GetZones() const noexcept { return m_zones; }
IFACEMETHODIMP_(ZonesMap)
GetZones()const noexcept override { return m_zones; }
IFACEMETHODIMP_(void)
MoveWindowIntoZoneByIndex(HWND window, HWND workAreaWindow, size_t index) noexcept;
IFACEMETHODIMP_(void)
@@ -157,7 +159,7 @@ private:
bool CalculateCustomLayout(Rect workArea, int spacing) noexcept;
bool CalculateGridZones(Rect workArea, FancyZonesDataTypes::GridLayoutInfo gridLayoutInfo, int spacing);
std::vector<winrt::com_ptr<IZone>> m_zones;
ZonesMap m_zones;
std::map<HWND, std::vector<size_t>> m_windowIndexSet;
// Needed for ExtendWindowByDirectionAndPosition
@@ -170,7 +172,12 @@ private:
IFACEMETHODIMP ZoneSet::AddZone(winrt::com_ptr<IZone> zone) noexcept
{
m_zones.emplace_back(zone);
auto zoneId = zone->Id();
if (m_zones.contains(zoneId))
{
return S_FALSE;
}
m_zones[zoneId] = zone;
return S_OK;
}
@@ -180,19 +187,19 @@ ZoneSet::ZonesFromPoint(POINT pt) const noexcept
{
std::vector<size_t> capturedZones;
std::vector<size_t> strictlyCapturedZones;
for (size_t i = 0; i < m_zones.size(); i++)
for (const auto& [zoneId, zone] : m_zones)
{
const RECT& zoneRect = m_zones[i]->GetZoneRect();
const RECT& zoneRect = zone->GetZoneRect();
if (zoneRect.left - m_config.SensitivityRadius <= pt.x && pt.x <= zoneRect.right + m_config.SensitivityRadius &&
zoneRect.top - m_config.SensitivityRadius <= pt.y && pt.y <= zoneRect.bottom + m_config.SensitivityRadius)
{
capturedZones.emplace_back(i);
capturedZones.emplace_back(zoneId);
}
if (zoneRect.left <= pt.x && pt.x < zoneRect.right &&
zoneRect.top <= pt.y && pt.y < zoneRect.bottom)
{
strictlyCapturedZones.emplace_back(i);
strictlyCapturedZones.emplace_back(zoneId);
}
}
@@ -210,8 +217,18 @@ ZoneSet::ZonesFromPoint(POINT pt) const noexcept
{
for (size_t j = i + 1; j < capturedZones.size(); ++j)
{
const auto& rectI = m_zones[capturedZones[i]]->GetZoneRect();
const auto& rectJ = m_zones[capturedZones[j]]->GetZoneRect();
RECT rectI;
RECT rectJ;
try
{
rectI = m_zones.at(capturedZones[i])->GetZoneRect();
rectJ = m_zones.at(capturedZones[j])->GetZoneRect();
}
catch (std::out_of_range)
{
return {};
}
if (max(rectI.top, rectJ.top) + m_config.SensitivityRadius < min(rectI.bottom, rectJ.bottom) &&
max(rectI.left, rectJ.left) + m_config.SensitivityRadius < min(rectI.right, rectJ.right))
{
@@ -230,8 +247,17 @@ ZoneSet::ZonesFromPoint(POINT pt) const noexcept
size_t smallestIdx = 0;
for (size_t i = 1; i < capturedZones.size(); ++i)
{
const auto& rectS = m_zones[capturedZones[smallestIdx]]->GetZoneRect();
const auto& rectI = m_zones[capturedZones[i]]->GetZoneRect();
RECT rectS;
RECT rectI;
try
{
rectS = m_zones.at(capturedZones[smallestIdx])->GetZoneRect();
rectI = m_zones.at(capturedZones[i])->GetZoneRect();
}
catch (std::out_of_range)
{
return {};
}
int smallestSize = (rectS.bottom - rectS.top) * (rectS.right - rectS.left);
int iSize = (rectI.bottom - rectI.top) * (rectI.right - rectI.left);
@@ -267,7 +293,7 @@ ZoneSet::MoveWindowIntoZoneByIndex(HWND window, HWND workAreaWindow, size_t inde
}
IFACEMETHODIMP_(void)
ZoneSet::MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const std::vector<size_t>& indexSet) noexcept
ZoneSet::MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const std::vector<size_t>& zoneIds) noexcept
{
if (m_zones.empty())
{
@@ -287,11 +313,12 @@ ZoneSet::MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const st
m_windowIndexSet[window] = {};
for (size_t index : indexSet)
for (size_t id : zoneIds)
{
if (index < m_zones.size())
if (m_zones.contains(id))
{
RECT newSize = m_zones.at(index)->ComputeActualZoneRect(window, workAreaWindow);
const auto& zone = m_zones.at(id);
const RECT newSize = zone->ComputeActualZoneRect(window, workAreaWindow);
if (!sizeEmpty)
{
size.left = min(size.left, newSize.left);
@@ -305,12 +332,12 @@ ZoneSet::MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const st
sizeEmpty = false;
}
m_windowIndexSet[window].push_back(index);
m_windowIndexSet[window].push_back(id);
}
if (index < std::numeric_limits<size_t>::digits)
if (id < std::numeric_limits<size_t>::digits)
{
bitmask |= 1ull << index;
bitmask |= 1ull << id;
}
}
@@ -336,14 +363,14 @@ ZoneSet::MoveWindowIntoZoneByDirectionAndIndex(HWND window, HWND workAreaWindow,
// The window was not assigned to any zone here
if (indexSet.size() == 0)
{
MoveWindowIntoZoneByIndexSet(window, workAreaWindow, { vkCode == VK_LEFT ? numZones - 1 : 0 });
MoveWindowIntoZoneByIndex(window, workAreaWindow, vkCode == VK_LEFT ? numZones - 1 : 0);
return true;
}
size_t oldIndex = indexSet[0];
size_t oldId = indexSet[0];
// We reached the edge
if ((vkCode == VK_LEFT && oldIndex == 0) || (vkCode == VK_RIGHT && oldIndex == numZones - 1))
if ((vkCode == VK_LEFT && oldId == 0) || (vkCode == VK_RIGHT && oldId == numZones - 1))
{
if (!cycle)
{
@@ -352,7 +379,7 @@ ZoneSet::MoveWindowIntoZoneByDirectionAndIndex(HWND window, HWND workAreaWindow,
}
else
{
MoveWindowIntoZoneByIndexSet(window, workAreaWindow, { vkCode == VK_LEFT ? numZones - 1 : 0 });
MoveWindowIntoZoneByIndex(window, workAreaWindow, vkCode == VK_LEFT ? numZones - 1 : 0);
return true;
}
}
@@ -360,11 +387,11 @@ ZoneSet::MoveWindowIntoZoneByDirectionAndIndex(HWND window, HWND workAreaWindow,
// We didn't reach the edge
if (vkCode == VK_LEFT)
{
MoveWindowIntoZoneByIndexSet(window, workAreaWindow, { oldIndex - 1 });
MoveWindowIntoZoneByIndex(window, workAreaWindow, oldId - 1);
}
else
{
MoveWindowIntoZoneByIndexSet(window, workAreaWindow, { oldIndex + 1 });
MoveWindowIntoZoneByIndex(window, workAreaWindow, oldId + 1);
}
return true;
}
@@ -378,20 +405,20 @@ ZoneSet::MoveWindowIntoZoneByDirectionAndPosition(HWND window, HWND workAreaWind
}
std::vector<bool> usedZoneIndices(m_zones.size(), false);
for (size_t idx : GetZoneIndexSetFromWindow(window))
for (size_t id : GetZoneIndexSetFromWindow(window))
{
usedZoneIndices[idx] = true;
usedZoneIndices[id] = true;
}
std::vector<RECT> zoneRects;
std::vector<size_t> freeZoneIndices;
for (size_t i = 0; i < m_zones.size(); i++)
for (const auto& [zoneId, zone] : m_zones)
{
if (!usedZoneIndices[i])
if (!usedZoneIndices[zoneId])
{
zoneRects.emplace_back(m_zones[i]->GetZoneRect());
freeZoneIndices.emplace_back(i);
zoneRects.emplace_back(m_zones[zoneId]->GetZoneRect());
freeZoneIndices.emplace_back(zoneId);
}
}
@@ -415,7 +442,7 @@ ZoneSet::MoveWindowIntoZoneByDirectionAndPosition(HWND window, HWND workAreaWind
// Try again from the position off the screen in the opposite direction to vkCode
// Consider all zones as available
zoneRects.resize(m_zones.size());
std::transform(m_zones.begin(), m_zones.end(), zoneRects.begin(), [](auto zone) { return zone->GetZoneRect(); });
std::transform(m_zones.begin(), m_zones.end(), zoneRects.begin(), [](auto zone) { return zone.second->GetZoneRect(); });
windowRect = FancyZonesUtils::PrepareRectForCycling(windowRect, windowZoneRect, vkCode);
result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
@@ -588,7 +615,7 @@ bool ZoneSet::CalculateFocusLayout(Rect workArea, int zoneCount) noexcept
for (int i = 0; i < zoneCount; i++)
{
auto zone = MakeZone(focusZoneRect, m_zones.size() + 1);
auto zone = MakeZone(focusZoneRect, m_zones.size());
if (zone)
{
AddZone(zone);
@@ -645,7 +672,7 @@ bool ZoneSet::CalculateColumnsAndRowsLayout(Rect workArea, FancyZonesDataTypes::
}
auto zone = MakeZone(RECT{ left, top, right, bottom }, m_zones.size() + 1);
auto zone = MakeZone(RECT{ left, top, right, bottom }, m_zones.size());
if (zone)
{
AddZone(zone);
@@ -713,9 +740,9 @@ bool ZoneSet::CalculateGridLayout(Rect workArea, FancyZonesDataTypes::ZoneSetLay
}
int index = 0;
for (int col = columns - 1; col >= 0; col--)
for (int row = 0; row < rows; row++)
{
for (int row = rows - 1; row >= 0; row--)
for (int col = 0; col < columns; col++)
{
gridLayoutInfo.cellChildMap()[row][col] = index++;
if (index == zoneCount)
@@ -765,7 +792,7 @@ bool ZoneSet::CalculateCustomLayout(Rect workArea, int spacing) noexcept
DPIAware::Convert(m_config.Monitor, x, y);
DPIAware::Convert(m_config.Monitor, width, height);
auto zone = MakeZone(RECT{ x, y, x + width, y + height }, m_zones.size() + 1);
auto zone = MakeZone(RECT{ x, y, x + width, y + height }, m_zones.size());
if (zone)
{
AddZone(zone);
@@ -848,7 +875,7 @@ bool ZoneSet::CalculateGridZones(Rect workArea, FancyZonesDataTypes::GridLayoutI
long right = columnInfo[maxCol].End;
long bottom = rowInfo[maxRow].End;
auto zone = MakeZone(RECT{ left, top, right, bottom }, m_zones.size() + 1);
auto zone = MakeZone(RECT{ left, top, right, bottom }, i);
if (zone)
{
AddZone(zone);
@@ -873,30 +900,32 @@ std::vector<size_t> ZoneSet::GetCombinedZoneRange(const std::vector<size_t>& ini
RECT boundingRect;
bool boundingRectEmpty = true;
auto zones = GetZones();
for (size_t zoneId : combinedZones)
{
const RECT& rect = zones[zoneId]->GetZoneRect();
if (boundingRectEmpty)
if (m_zones.contains(zoneId))
{
boundingRect = rect;
boundingRectEmpty = false;
}
else
{
boundingRect.left = min(boundingRect.left, rect.left);
boundingRect.top = min(boundingRect.top, rect.top);
boundingRect.right = max(boundingRect.right, rect.right);
boundingRect.bottom = max(boundingRect.bottom, rect.bottom);
const RECT rect = m_zones.at(zoneId)->GetZoneRect();
if (boundingRectEmpty)
{
boundingRect = rect;
boundingRectEmpty = false;
}
else
{
boundingRect.left = min(boundingRect.left, rect.left);
boundingRect.top = min(boundingRect.top, rect.top);
boundingRect.right = max(boundingRect.right, rect.right);
boundingRect.bottom = max(boundingRect.bottom, rect.bottom);
}
}
}
if (!boundingRectEmpty)
{
for (size_t zoneId = 0; zoneId < zones.size(); zoneId++)
for (const auto& [zoneId, zone] : m_zones)
{
RECT rect = zones[zoneId]->GetZoneRect();
const RECT rect = zone->GetZoneRect();
if (boundingRect.left <= rect.left && rect.right <= boundingRect.right &&
boundingRect.top <= rect.top && rect.bottom <= boundingRect.bottom)
{

View File

@@ -6,13 +6,15 @@ namespace FancyZonesDataTypes
{
enum class ZoneSetLayoutType;
}
/**
* Class representing single zone layout. ZoneSet is responsible for actual calculation of rectangle coordinates
* (whether is grid or canvas layout) and moving windows through them.
*/
interface __declspec(uuid("{E4839EB7-669D-49CF-84A9-71A2DFD851A3}")) IZoneSet : public IUnknown
{
// Mapping zone id to zone
using ZonesMap = std::map<size_t, winrt::com_ptr<IZone>>;
/**
* @returns Unique identifier of zone layout.
*/
@@ -45,7 +47,7 @@ interface __declspec(uuid("{E4839EB7-669D-49CF-84A9-71A2DFD851A3}")) IZoneSet :
/**
* @returns Array of zone objects (defining coordinates of the zone) inside this zone layout.
*/
IFACEMETHOD_(std::vector<winrt::com_ptr<IZone>>, GetZones)() const = 0;
IFACEMETHOD_(ZonesMap, GetZones) () const = 0;
/**
* Assign window to the zone based on zone index inside zone layout.
*

View File

@@ -69,7 +69,7 @@ namespace ZoneWindowUtils
COLORREF hostZoneBorderColor,
COLORREF hostZoneHighlightColor,
int hostZoneHighlightOpacity,
std::vector<winrt::com_ptr<IZone>> zones,
IZoneSet::ZonesMap zones,
std::vector<size_t> highlightZone,
bool flashMode)
{
@@ -621,7 +621,7 @@ void ZoneWindow::OnPaint(HDC hdc) noexcept
COLORREF hostZoneBorderColor{};
COLORREF hostZoneHighlightColor{};
int hostZoneHighlightOpacity{};
std::vector<winrt::com_ptr<IZone>> zones{};
IZoneSet::ZonesMap zones;
std::vector<size_t> highlightZone = m_highlightZone;
bool flashMode = m_flashMode;

View File

@@ -1,7 +1,10 @@
#include "pch.h"
#include "ZoneWindowDrawing.h"
#include <algorithm>
#include <map>
#include <string>
#include <vector>
namespace NonLocalizable
{
@@ -79,7 +82,7 @@ namespace
g.DrawString(text.c_str(), -1, &font, gdiRect, &stringFormat, &solidBrush);
}
void DrawZone(wil::unique_hdc& hdc, ZoneWindowDrawing::ColorSetting const& colorSetting, winrt::com_ptr<IZone> zone, const std::vector<winrt::com_ptr<IZone>>& zones, bool flashMode) noexcept
void DrawZone(wil::unique_hdc& hdc, ZoneWindowDrawing::ColorSetting const& colorSetting, winrt::com_ptr<IZone> zone, bool flashMode)noexcept
{
RECT zoneRect = zone->GetZoneRect();
@@ -95,7 +98,7 @@ namespace
if (!flashMode)
{
DrawIndex(hdc, zoneRect, zone->Id());
DrawIndex(hdc, zoneRect, zone->Id() + 1);
}
}
}
@@ -112,7 +115,7 @@ namespace ZoneWindowDrawing
COLORREF zoneBorderColor,
COLORREF highlightColor,
int zoneOpacity,
const std::vector<winrt::com_ptr<IZone>>& zones,
const IZoneSet::ZonesMap& zones,
const std::vector<size_t>& highlightZones,
bool flashMode) noexcept
{
@@ -121,52 +124,41 @@ namespace ZoneWindowDrawing
ColorSetting colorHighlight{ OpacitySettingToAlpha(zoneOpacity), 0, 255, 0, -2 };
ColorSetting const colorFlash{ OpacitySettingToAlpha(zoneOpacity), RGB(81, 92, 107), 200, RGB(104, 118, 138), -2 };
std::vector<bool> isHighlighted(zones.size(), false);
for (size_t x : highlightZones)
{
isHighlighted[x] = true;
}
// First draw the inactive zones
for (auto iter = zones.begin(); iter != zones.end(); iter++)
{
int zoneId = static_cast<int>(iter - zones.begin());
winrt::com_ptr<IZone> zone = iter->try_as<IZone>();
winrt::com_ptr<IZone> zone = iter->second;
size_t zoneId = zone->Id();
if (!zone)
{
continue;
}
if (!isHighlighted[zoneId])
auto zoneIt = std::find(highlightZones.begin(), highlightZones.end(), zoneId);
if (zoneIt == highlightZones.end())
{
if (flashMode)
{
DrawZone(hdc, colorFlash, zone, zones, flashMode);
DrawZone(hdc, colorFlash, zone, flashMode);
}
else
{
colorViewer.fill = zoneColor;
colorViewer.border = zoneBorderColor;
DrawZone(hdc, colorViewer, zone, zones, flashMode);
DrawZone(hdc, colorViewer, zone, flashMode);
}
}
}
// Draw the active zones on top of the inactive zones
for (auto iter = zones.begin(); iter != zones.end(); iter++)
for (const auto& zoneId : highlightZones)
{
int zoneId = static_cast<int>(iter - zones.begin());
winrt::com_ptr<IZone> zone = iter->try_as<IZone>();
if (!zone)
{
continue;
}
colorHighlight.fill = highlightColor;
colorHighlight.border = zoneBorderColor;
if (isHighlighted[zoneId])
if (zones.contains(zoneId))
{
colorHighlight.fill = highlightColor;
colorHighlight.border = zoneBorderColor;
DrawZone(hdc, colorHighlight, zone, zones, flashMode);
DrawZone(hdc, colorHighlight, zones.at(zoneId), flashMode);
}
}
}

View File

@@ -1,11 +1,13 @@
#pragma once
#include <map>
#include <vector>
#include <wil\resource.h>
#include <winrt/base.h>
#include "util.h"
#include "Zone.h"
#include "ZoneSet.h"
namespace ZoneWindowDrawing
{
@@ -24,7 +26,7 @@ namespace ZoneWindowDrawing
COLORREF zoneBorderColor,
COLORREF highlightColor,
int zoneOpacity,
const std::vector<winrt::com_ptr<IZone>>& zones,
const IZoneSet::ZonesMap& zones,
const std::vector<size_t>& highlightZones,
bool flashMode) noexcept;
}

View File

@@ -85,28 +85,28 @@ namespace FancyZonesUnitTests
TEST_METHOD (AddOne)
{
constexpr size_t zoneId = 1;
constexpr size_t zoneId = 0;
winrt::com_ptr<IZone> zone = MakeZone({ 0, 0, 100, 100 }, zoneId);
Assert::IsNotNull(zone.get());
m_set->AddZone(zone);
auto zones = m_set->GetZones();
Assert::AreEqual((size_t)1, zones.size());
compareZones(zone, zones[0]);
Assert::AreEqual(zoneId, zones[0]->Id());
compareZones(zone, zones[zoneId]);
Assert::AreEqual(zoneId, zones[zoneId]->Id());
}
TEST_METHOD (AddManyEqual)
{
for (size_t i = 0; i < 1024; i++)
{
size_t zoneId = i + 1;
size_t zoneId = i;
winrt::com_ptr<IZone> zone = MakeZone({ 0, 0, 100, 100 }, zoneId);
Assert::IsNotNull(zone.get());
m_set->AddZone(zone);
auto zones = m_set->GetZones();
Assert::AreEqual(i + 1, zones.size());
compareZones(zone, zones[i]);
Assert::AreEqual(zoneId, zones[i]->Id());
compareZones(zone, zones[zoneId]);
Assert::AreEqual(zoneId, zones[zoneId]->Id());
}
}
@@ -114,7 +114,7 @@ namespace FancyZonesUnitTests
{
for (size_t i = 0; i < 1024; i++)
{
size_t zoneId = i + 1;
size_t zoneId = i;
int left = rand() % 10;
int top = rand() % 10;
int right = left + 1 + rand() % 100;
@@ -124,8 +124,8 @@ namespace FancyZonesUnitTests
m_set->AddZone(zone);
auto zones = m_set->GetZones();
Assert::AreEqual(i + 1, zones.size());
compareZones(zone, zones[i]);
Assert::AreEqual(zoneId, zones[i]->Id());
compareZones(zone, zones[zoneId]);
Assert::AreEqual(zoneId, zones[zoneId]->Id());
}
}
@@ -135,13 +135,6 @@ namespace FancyZonesUnitTests
Assert::IsNotNull(zone.get());
}
TEST_METHOD (MakeZoneWithInvalidId)
{
constexpr size_t invalidZoneId = 0;
winrt::com_ptr<IZone> zone = MakeZone({ 0, 0, 0, 0 }, invalidZoneId);
Assert::IsNull(zone.get());
}
TEST_METHOD (MakeZoneFromInvalidRectWidth)
{
winrt::com_ptr<IZone> zone = MakeZone({ 100, 100, 99, 101 }, 1);
@@ -361,9 +354,9 @@ namespace FancyZonesUnitTests
TEST_METHOD (MoveWindowIntoZoneByIndexSeveralTimesSameWindow)
{
// Add a couple of zones.
winrt::com_ptr<IZone> zone1 = MakeZone({ 0, 0, 100, 100 }, 1);
winrt::com_ptr<IZone> zone2 = MakeZone({ 1, 1, 101, 101 }, 2);
winrt::com_ptr<IZone> zone3 = MakeZone({ 2, 2, 102, 102 }, 3);
winrt::com_ptr<IZone> zone1 = MakeZone({ 0, 0, 100, 100 }, 0);
winrt::com_ptr<IZone> zone2 = MakeZone({ 1, 1, 101, 101 }, 1);
winrt::com_ptr<IZone> zone3 = MakeZone({ 2, 2, 102, 102 }, 2);
m_set->AddZone(zone1);
m_set->AddZone(zone2);
m_set->AddZone(zone3);
@@ -382,9 +375,9 @@ namespace FancyZonesUnitTests
TEST_METHOD (MoveWindowIntoZoneByIndexSeveralTimesSameIndex)
{
// Add a couple of zones.
winrt::com_ptr<IZone> zone1 = MakeZone({ 0, 0, 100, 100 }, 1);
winrt::com_ptr<IZone> zone2 = MakeZone({ 1, 1, 101, 101 }, 2);
winrt::com_ptr<IZone> zone3 = MakeZone({ 2, 2, 102, 102 }, 3);
winrt::com_ptr<IZone> zone1 = MakeZone({ 0, 0, 100, 100 }, 0);
winrt::com_ptr<IZone> zone2 = MakeZone({ 1, 1, 101, 101 }, 1);
winrt::com_ptr<IZone> zone3 = MakeZone({ 2, 2, 102, 102 }, 2);
m_set->AddZone(zone1);
m_set->AddZone(zone2);
m_set->AddZone(zone3);
@@ -414,7 +407,7 @@ namespace FancyZonesUnitTests
TEST_METHOD (MoveWindowIntoZoneByPointInnerPoint)
{
winrt::com_ptr<IZone> zone1 = MakeZone({ 0, 0, 100, 100 }, 1);
winrt::com_ptr<IZone> zone1 = MakeZone({ 0, 0, 100, 100 }, 0);
m_set->AddZone(zone1);
auto window = Mocks::Window();
@@ -425,8 +418,8 @@ namespace FancyZonesUnitTests
TEST_METHOD (MoveWindowIntoZoneByPointInnerPointOverlappingZones)
{
winrt::com_ptr<IZone> zone1 = MakeZone({ 0, 0, 100, 100 }, 1);
winrt::com_ptr<IZone> zone2 = MakeZone({ 10, 10, 90, 90 }, 2);
winrt::com_ptr<IZone> zone1 = MakeZone({ 0, 0, 100, 100 }, 0);
winrt::com_ptr<IZone> zone2 = MakeZone({ 10, 10, 90, 90 }, 1);
m_set->AddZone(zone1);
m_set->AddZone(zone2);
@@ -441,8 +434,8 @@ namespace FancyZonesUnitTests
const auto window = Mocks::Window();
const auto zoneWindow = Mocks::Window();
winrt::com_ptr<IZone> zone1 = MakeZone({ 0, 0, 100, 100 }, 1);
winrt::com_ptr<IZone> zone2 = MakeZone({ 10, 10, 90, 90 }, 2);
winrt::com_ptr<IZone> zone1 = MakeZone({ 0, 0, 100, 100 }, 0);
winrt::com_ptr<IZone> zone2 = MakeZone({ 10, 10, 90, 90 }, 1);
m_set->AddZone(zone1);
m_set->AddZone(zone2);
@@ -459,8 +452,8 @@ namespace FancyZonesUnitTests
const auto window = Mocks::Window();
const auto zoneWindow = Mocks::Window();
winrt::com_ptr<IZone> zone1 = MakeZone({ 0, 0, 100, 100 }, 1);
winrt::com_ptr<IZone> zone2 = MakeZone({ 10, 10, 90, 90 }, 2);
winrt::com_ptr<IZone> zone1 = MakeZone({ 0, 0, 100, 100 }, 0);
winrt::com_ptr<IZone> zone2 = MakeZone({ 10, 10, 90, 90 }, 1);
m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 1);
@@ -477,9 +470,9 @@ namespace FancyZonesUnitTests
const auto window = Mocks::Window();
const auto zoneWindow = Mocks::Window();
winrt::com_ptr<IZone> zone1 = MakeZone({ 0, 0, 100, 100 }, 1);
winrt::com_ptr<IZone> zone2 = MakeZone({ 10, 10, 90, 90 }, 2);
winrt::com_ptr<IZone> zone3 = MakeZone({ 20, 20, 80, 80 }, 3);
winrt::com_ptr<IZone> zone1 = MakeZone({ 0, 0, 100, 100 }, 0);
winrt::com_ptr<IZone> zone2 = MakeZone({ 10, 10, 90, 90 }, 1);
winrt::com_ptr<IZone> zone3 = MakeZone({ 20, 20, 80, 80 }, 2);
m_set->AddZone(zone1);
m_set->AddZone(zone2);
@@ -507,9 +500,9 @@ namespace FancyZonesUnitTests
m_set = MakeZoneSet(config);
// Add a couple of zones.
m_zone1 = MakeZone({ 0, 0, 100, 100 }, 1);
m_zone2 = MakeZone({ 0, 0, 100, 100 }, 2);
m_zone3 = MakeZone({ 0, 0, 100, 100 }, 3);
m_zone1 = MakeZone({ 0, 0, 100, 100 }, 0);
m_zone2 = MakeZone({ 0, 0, 100, 100 }, 1);
m_zone3 = MakeZone({ 0, 0, 100, 100 }, 2);
m_set->AddZone(m_zone1);
m_set->AddZone(m_zone2);
m_set->AddZone(m_zone3);
@@ -776,7 +769,7 @@ namespace FancyZonesUnitTests
{
Assert::IsTrue(set->IsZoneEmpty(zoneId));
const auto& zoneRect = zone->GetZoneRect();
const auto& zoneRect = zone.second->GetZoneRect();
Assert::IsTrue(zoneRect.left >= 0, L"left border is less than zero");
Assert::IsTrue(zoneRect.top >= 0, L"top border is less than zero");

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 110 KiB

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);
}
}
}