Merged powerToys master into Launcher master

This commit is contained in:
Alekhya Reddy
2020-04-08 11:49:32 -07:00
373 changed files with 33739 additions and 7151 deletions

View File

@@ -46,10 +46,12 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>

View File

@@ -48,10 +48,12 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>

View File

@@ -12,6 +12,10 @@
<!-- Accent and AppTheme setting -->
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/Light.Blue.xaml" />
</ResourceDictionary.MergedDictionaries>
<SolidColorBrush x:Key="CanvasZoneBackgroundBrush" Color="#BF333333"/>
<SolidColorBrush x:Key="GridZoneBackgroundBrush" Color="#FF1a1a1a"/>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -55,6 +55,12 @@ namespace FancyZonesEditor
previewChildrenCount++;
}
while (previewChildrenCount > _model.Zones.Count)
{
Preview.Children.RemoveAt(previewChildrenCount - 1);
previewChildrenCount--;
}
for (int i = 0; i < previewChildrenCount; i++)
{
Int32Rect rect = _model.Zones[i];
@@ -65,6 +71,7 @@ namespace FancyZonesEditor
Canvas.SetTop(zone, rect.Y);
zone.Height = rect.Height;
zone.Width = rect.Width;
zone.LabelID.Content = i + 1;
}
}
}

View File

@@ -16,6 +16,7 @@ namespace FancyZonesEditor
{
InitializeComponent();
_model = EditorOverlay.Current.DataContext as CanvasLayoutModel;
_stashedModel = (CanvasLayoutModel)_model.Clone();
}
private void OnAddZone(object sender, RoutedEventArgs e)
@@ -24,7 +25,14 @@ namespace FancyZonesEditor
_offset += 100;
}
protected new void OnCancel(object sender, RoutedEventArgs e)
{
base.OnCancel(sender, e);
_stashedModel.RestoreTo(_model);
}
private int _offset = 100;
private CanvasLayoutModel _model;
private CanvasLayoutModel _stashedModel;
}
}

View File

@@ -5,40 +5,113 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:FancyZonesEditor"
mc:Ignorable="d"
Background="LightGray"
Opacity="0.75"
Background="Transparent"
d:DesignHeight="450" d:DesignWidth="800">
<Grid x:Name="Frame">
<Grid.RowDefinitions>
<RowDefinition Height="8"/>
<RowDefinition Height="16"/>
<RowDefinition Height="24"/>
<RowDefinition Height="*"/>
<RowDefinition Height="16"/>
<RowDefinition Height="8"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="8"/>
</Grid.ColumnDefinitions>
<Thumb x:Name="NWResize" Cursor="SizeNWSE" Background="Black" Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Grid.ColumnSpan="2" DragDelta="UniversalDragDelta" DragStarted="NWResize_DragStarted"/>
<Thumb x:Name="NEResize" Cursor="SizeNESW" Background="Black" Grid.Row="0" Grid.Column="3" Grid.RowSpan="2" Grid.ColumnSpan="2" DragDelta="UniversalDragDelta" DragStarted="NEResize_DragStarted"/>
<Thumb x:Name="SWResize" Cursor="SizeNESW" Background="Black" Grid.Row="4" Grid.Column="0" Grid.RowSpan="2" Grid.ColumnSpan="2" DragDelta="UniversalDragDelta" DragStarted="SWResize_DragStarted"/>
<Thumb x:Name="SEResize" Cursor="SizeNWSE" Background="Black" Grid.Row="4" Grid.Column="3" Grid.RowSpan="2" Grid.ColumnSpan="2" DragDelta="UniversalDragDelta" DragStarted="SEResize_DragStarted"/>
<Thumb x:Name="NResize" Cursor="SizeNS" Background="Black" Margin="1,0,1,0" Grid.Row="0" Grid.Column="2" DragDelta="UniversalDragDelta" DragStarted="NResize_DragStarted"/>
<Thumb x:Name="SResize" Cursor="SizeNS" Background="Black" Margin="1,0,1,0" Grid.Row="5" Grid.Column="2" DragDelta="UniversalDragDelta" DragStarted="SResize_DragStarted"/>
<Thumb x:Name="WResize" Cursor="SizeWE" Background="Black" Margin="0,1,0,1" Grid.Row="2" Grid.Column="0" Grid.RowSpan="2" DragDelta="UniversalDragDelta" DragStarted="WResize_DragStarted"/>
<Thumb x:Name="EResize" Cursor="SizeWE" Background="Black" Margin="0,1,0,1" Grid.Row="2" Grid.Column="4" Grid.RowSpan="2" DragDelta="UniversalDragDelta" DragStarted="EResize_DragStarted"/>
<DockPanel Grid.Row="1" Grid.Column="1" Grid.RowSpan="2" Grid.ColumnSpan="3">
<Button DockPanel.Dock="Right" Padding="8,0" Click="OnClose">
<Image Source="images/ChromeClose.png" Height="24" Width="24" />
</Button>
<Thumb x:Name="Caption" Cursor="SizeAll" Background="DarkGray" DragDelta="UniversalDragDelta" DragStarted="Caption_DragStarted"/>
</DockPanel>
<Rectangle Fill="LightGray" Grid.Row="3" Grid.Column="1" Grid.RowSpan="2" Grid.ColumnSpan="3"/>
<Canvas x:Name="Body" />
</Grid>
<UserControl.Resources>
<Style x:Key="CanvasZoneThumbStyle" TargetType="{x:Type Thumb}">
<Setter Property="Stylus.IsPressAndHoldEnabled" Value="false"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Border x:Name="ThumbBorder" Opacity="0" BorderBrush="{Binding Source={x:Static SystemParameters.WindowGlassBrush}}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates" >
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:0.15">
<VisualTransition.GeneratedEasingFunction>
<ExponentialEase EasingMode="EaseInOut"/>
</VisualTransition.GeneratedEasingFunction>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Normal" />
<VisualState x:Name="MouseOver">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="ThumbBorder" Duration="0:0:0.15" Storyboard.TargetProperty="Opacity" To="1"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CloseButtonStyle" TargetType="{x:Type Button}">
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
<ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsDefaulted" Value="true">
<Setter Property="BorderBrush" TargetName="border" Value="{Binding Source={x:Static SystemParameters.WindowGlassBrush}}"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Opacity" TargetName="contentPresenter" Value="0.6"/>
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter Property="Opacity" TargetName="contentPresenter" Value="0.4"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Border BorderBrush="{Binding Source={x:Static SystemParameters.WindowGlassBrush}}" Background="{StaticResource CanvasZoneBackgroundBrush}" BorderThickness="1">
<Grid x:Name="Frame">
<Grid.RowDefinitions>
<RowDefinition Height="8"/>
<RowDefinition Height="16"/>
<RowDefinition Height="*"/>
<RowDefinition Height="16"/>
<RowDefinition Height="8"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="8"/>
</Grid.ColumnDefinitions>
<Label Name="LabelID"
Content="ID"
Canvas.Left="10"
Canvas.Bottom="10"
FontSize="64"
FontFamily="Segoe UI Light"
Foreground="White"
Grid.Column="2"
Grid.Row="2"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Center" />
<Thumb x:Name="Caption" Cursor="SizeAll" Background="Transparent" BorderThickness="3" Padding="4" Grid.Column="0" Grid.ColumnSpan="5" Grid.Row="0" Grid.RowSpan="5" DragDelta="UniversalDragDelta" DragStarted="Caption_DragStarted" Style="{DynamicResource CanvasZoneThumbStyle}"/>
<Thumb x:Name="NResize" Cursor="SizeNS" BorderThickness="0,3,0,0" Grid.ColumnSpan="5" DragDelta="UniversalDragDelta" DragStarted="NResize_DragStarted" Style="{DynamicResource CanvasZoneThumbStyle}"/>
<Thumb x:Name="SResize" Cursor="SizeNS" BorderThickness="0,0,0,3" Grid.Row="4" Grid.ColumnSpan="5" DragDelta="UniversalDragDelta" DragStarted="SResize_DragStarted" Style="{DynamicResource CanvasZoneThumbStyle}"/>
<Thumb x:Name="WResize" Cursor="SizeWE" BorderThickness="3,0,0,0" Grid.RowSpan="5" DragDelta="UniversalDragDelta" DragStarted="WResize_DragStarted" Style="{DynamicResource CanvasZoneThumbStyle}"/>
<Thumb x:Name="EResize" Cursor="SizeWE" BorderThickness="0,0,3,0" Grid.Column="4" Grid.RowSpan="5" DragDelta="UniversalDragDelta" DragStarted="EResize_DragStarted" Style="{DynamicResource CanvasZoneThumbStyle}"/>
<Thumb x:Name="NWResize" Cursor="SizeNWSE" BorderThickness="3,3,0,0" Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Grid.ColumnSpan="2" DragDelta="UniversalDragDelta" DragStarted="NWResize_DragStarted" Style="{DynamicResource CanvasZoneThumbStyle}"/>
<Thumb x:Name="NEResize" Cursor="SizeNESW" BorderThickness="0,3,3,0" Grid.Row="0" Grid.Column="3" Grid.RowSpan="2" Grid.ColumnSpan="2" DragDelta="UniversalDragDelta" DragStarted="NEResize_DragStarted" Style="{DynamicResource CanvasZoneThumbStyle}"/>
<Thumb x:Name="SWResize" Cursor="SizeNESW" BorderThickness="3,0,0,3" Grid.Row="3" Grid.Column="0" Grid.RowSpan="2" Grid.ColumnSpan="2" DragDelta="UniversalDragDelta" DragStarted="SWResize_DragStarted" Style="{DynamicResource CanvasZoneThumbStyle}"/>
<Thumb x:Name="SEResize" Cursor="SizeNWSE" BorderThickness="0,0,3,3" Grid.Row="3" Grid.Column="3" Grid.RowSpan="2" Grid.ColumnSpan="2" DragDelta="UniversalDragDelta" DragStarted="SEResize_DragStarted" Style="{DynamicResource CanvasZoneThumbStyle}"/>
<Button Content="&#xE894;" BorderThickness="0" ToolTip="Delete zone" Background="Transparent" Foreground="White" FontSize="16" Padding="4" Click="OnClose" Grid.Row="2" Grid.Column="2" FontFamily="Segoe MDL2 Assets" HorizontalAlignment="Right" VerticalAlignment="Top" Style="{DynamicResource CloseButtonStyle}"/>
<Canvas x:Name="Body" />
</Grid>
</Border>
</UserControl>

View File

@@ -0,0 +1,20 @@
// 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.Text.Json;
using FancyZonesEditor.Utils;
namespace FancyZonesEditor
{
public class DashCaseNamingPolicy : JsonNamingPolicy
{
public static DashCaseNamingPolicy Instance { get; } = new DashCaseNamingPolicy();
public override string ConvertName(string name)
{
return name.UpperCamelCaseToDashCase();
}
}
}

View File

@@ -21,14 +21,14 @@ namespace FancyZonesEditor
LayoutModel.SerializeDeletedCustomZoneSets();
_choosing = true;
_backToLayoutPicker = false;
Close();
EditorOverlay.Current.Close();
}
protected void OnClosed(object sender, EventArgs e)
{
if (!_choosing)
if (_backToLayoutPicker)
{
EditorOverlay.Current.ShowLayoutPicker();
}
@@ -36,11 +36,10 @@ namespace FancyZonesEditor
protected void OnCancel(object sender, RoutedEventArgs e)
{
_choosing = true;
_backToLayoutPicker = true;
Close();
EditorOverlay.Current.ShowLayoutPicker();
}
private bool _choosing = false;
private bool _backToLayoutPicker = true;
}
}

View File

@@ -58,6 +58,7 @@
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<IntermediateOutputPath>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(AssemblyName)\</IntermediateOutputPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
<PlatformTarget>x64</PlatformTarget>
@@ -109,6 +110,8 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="DashCaseNamingPolicy.cs" />
<Compile Include="StringUtils.cs" />
<Compile Include="Converters\BooleanToBrushConverter.xaml.cs" />
<Compile Include="Converters\BooleanToIntConverter.xaml.cs" />
<Compile Include="CanvasEditor.xaml.cs">

View File

@@ -1,32 +1,48 @@
<UserControl x:Class="FancyZonesEditor.GridEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:FancyZonesEditor"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl
x:Class="FancyZonesEditor.GridEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:FancyZonesEditor"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<UserControl.Resources>
<Style TargetType="Button">
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Background" Value="#F2F2F2"/>
<Setter Property="Width" Value="150"/>
<Setter Property="Foreground" Value="Black" />
<Setter Property="FontSize" Value="14" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Background" Value="#F2F2F2" />
<Setter Property="Width" Value="150" />
</Style>
</UserControl.Resources>
<Grid>
<Canvas x:Name="Preview"/>
<Canvas x:Name="AdornerLayer"/>
<Canvas x:Name="MergePanel" Visibility="Collapsed" MouseUp="MergePanelMouseUp">
<Canvas x:Name="Preview" />
<Canvas x:Name="AdornerLayer" />
<Canvas
x:Name="MergePanel"
MouseUp="MergePanelMouseUp"
Visibility="Collapsed">
<StackPanel x:Name="MergeButtons" Background="Gray" Orientation="Horizontal">
<Button Click="MergeClick" Margin="0" Height="36" Width="134">
<StackPanel
x:Name="MergeButtons"
Background="Gray"
Orientation="Horizontal">
<Button
Width="134"
Height="36"
Margin="0"
Click="MergeClick">
<StackPanel Orientation="Horizontal">
<Image Source="images/Merge.png" Margin="0,0,12,0" Height="16" HorizontalAlignment="Left" />
<TextBlock Text="Merge zones"/>
<Image
Height="16"
Margin="0,0,12,0"
HorizontalAlignment="Left"
Source="images/Merge.png" />
<TextBlock Text="Merge zones" />
</StackPanel>
</Button>
</StackPanel>

View File

@@ -18,11 +18,16 @@ namespace FancyZonesEditor
{
public static readonly DependencyProperty ModelProperty = DependencyProperty.Register("Model", typeof(GridLayoutModel), typeof(GridEditor), new PropertyMetadata(null, OnGridDimensionsChanged));
private static int gridEditorUniqueIdCounter = 0;
private int gridEditorUniqueId;
public GridEditor()
{
InitializeComponent();
Loaded += GridEditor_Loaded;
((App)Application.Current).ZoneSettings.PropertyChanged += ZoneSettings_PropertyChanged;
gridEditorUniqueId = ++gridEditorUniqueIdCounter;
}
private void GridEditor_Loaded(object sender, RoutedEventArgs e)
@@ -73,7 +78,9 @@ namespace FancyZonesEditor
private void ZoneSettings_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
Size actualSize = new Size(ActualWidth, ActualHeight);
if (actualSize.Width > 0)
// Only enter if this is the newest instance
if (actualSize.Width > 0 && gridEditorUniqueId == gridEditorUniqueIdCounter)
{
ArrangeGridRects(actualSize);
}
@@ -134,83 +141,50 @@ namespace FancyZonesEditor
private void ExtendRangeToHaveEvenCellEdges()
{
// extend each edge of the [(_startCol, _startRow) - (_endCol, _endRow)] range based on merged cells until you have 4 straight edges with no "straddling cells"
// As long as there is an edge of the 2D range such that some zone crosses its boundary, extend
// that boundary. A single pass is not enough, a while loop is needed. This results in the unique
// smallest rectangle containing the initial range such that no zone is "broken", meaning that
// some part of it is inside the 2D range, and some part is outside.
GridLayoutModel model = Model;
bool possiblyBroken = true;
while (_startRow > 0)
while (possiblyBroken)
{
bool dirty = false;
possiblyBroken = false;
for (int col = _startCol; col <= _endCol; col++)
{
if (model.CellChildMap[_startRow - 1, col] == model.CellChildMap[_startRow, col])
if (_startRow > 0 && model.CellChildMap[_startRow - 1, col] == model.CellChildMap[_startRow, col])
{
_startRow--;
dirty = true;
possiblyBroken = true;
break;
}
}
if (!dirty)
{
break;
}
}
while (_endRow < model.Rows - 1)
{
bool dirty = false;
for (int col = _startCol; col <= _endCol; col++)
{
if (model.CellChildMap[_endRow + 1, col] == model.CellChildMap[_endRow, col])
if (_endRow < model.Rows - 1 && model.CellChildMap[_endRow + 1, col] == model.CellChildMap[_endRow, col])
{
_endRow++;
dirty = true;
possiblyBroken = true;
break;
}
}
if (!dirty)
{
break;
}
}
while (_startCol > 0)
{
bool dirty = false;
for (int row = _startRow; row <= _endRow; row++)
{
if (model.CellChildMap[row, _startCol - 1] == model.CellChildMap[row, _startCol])
if (_startCol > 0 && model.CellChildMap[row, _startCol - 1] == model.CellChildMap[row, _startCol])
{
_startCol--;
dirty = true;
possiblyBroken = true;
break;
}
}
if (!dirty)
{
break;
}
}
while (_endCol < model.Columns - 1)
{
bool dirty = false;
for (int row = _startRow; row <= _endRow; row++)
{
if (model.CellChildMap[row, _endCol + 1] == model.CellChildMap[row, _endCol])
if (_endCol < model.Columns - 1 && model.CellChildMap[row, _endCol + 1] == model.CellChildMap[row, _endCol])
{
_endCol++;
dirty = true;
possiblyBroken = true;
break;
}
}
if (!dirty)
{
break;
}
}
}
@@ -528,7 +502,8 @@ namespace FancyZonesEditor
private void OnGridDimensionsChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if ((e.PropertyName == "Rows") || (e.PropertyName == "Columns"))
// Only enter if this is the newest instance
if (((e.PropertyName == "Rows") || (e.PropertyName == "Columns")) && gridEditorUniqueId == gridEditorUniqueIdCounter)
{
OnGridDimensionsChanged();
}
@@ -594,6 +569,7 @@ namespace FancyZonesEditor
top = _rowInfo[row].Start;
Canvas.SetLeft(zone, left);
Canvas.SetTop(zone, top);
zone.LabelID.Content = i + 1;
int maxRow = row;
while (((maxRow + 1) < rows) && (model.CellChildMap[maxRow + 1, col] == i))

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.Windows;
using FancyZonesEditor.Models;
namespace FancyZonesEditor
{
/// <summary>
@@ -12,6 +15,16 @@ namespace FancyZonesEditor
public GridEditorWindow()
{
InitializeComponent();
_stashedModel = (GridLayoutModel)(EditorOverlay.Current.DataContext as GridLayoutModel).Clone();
}
protected new void OnCancel(object sender, RoutedEventArgs e)
{
base.OnCancel(sender, e);
GridLayoutModel model = EditorOverlay.Current.DataContext as GridLayoutModel;
_stashedModel.RestoreTo(model);
}
private GridLayoutModel _stashedModel;
}
}

View File

@@ -9,10 +9,16 @@
<Thumb.Template>
<ControlTemplate>
<StackPanel x:Name="Body" Grid.Column="0" Width="48" Height="48">
<Ellipse Height="48" Width="48" Fill="#0078D7" />
<Ellipse x:Name="BackgroundEllipse" Height="48" Width="48" Fill="{Binding Source={x:Static SystemParameters.WindowGlassBrush}}" />
<Rectangle Height="20" Width="2" Fill="White" Margin="5,-48,0,0"/>
<Rectangle Height="20" Width="2" Fill="White" Margin="-5,-48,0,0"/>
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Opacity" TargetName="BackgroundEllipse" Value="0.6"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Thumb.Template>
</Thumb>

View File

@@ -1,17 +1,31 @@
<UserControl x:Class="FancyZonesEditor.GridZone"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:FancyZonesEditor"
mc:Ignorable="d"
Background="LightGray"
BorderThickness="1"
BorderBrush="DarkGray"
Opacity="0.5"
d:DesignHeight="450" d:DesignWidth="800">
<Grid x:Name="Frame" Visibility="Collapsed">
<UserControl
x:Class="FancyZonesEditor.GridZone"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:FancyZonesEditor"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="450"
d:DesignWidth="800"
Background="{StaticResource GridZoneBackgroundBrush}"
BorderBrush="{Binding Source={x:Static SystemParameters.WindowGlassBrush}}"
BorderThickness="1"
Opacity="0.8"
mc:Ignorable="d">
<Grid x:Name="Frame">
<Canvas x:Name="Body" />
<Label
Name="LabelID"
Grid.Row="3"
Grid.Column="2"
Canvas.Left="10"
Canvas.Bottom="10"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Content="ID"
FontFamily="Segoe UI Light"
FontSize="64"
Foreground="White" />
<!--<TextBlock Margin="2" Text="Shift Key switches direction&#13;Ctrl Key repeats"/>-->
</Grid>
</UserControl>

View File

@@ -45,7 +45,7 @@ namespace FancyZonesEditor
private void OnSelectionChanged()
{
Background = IsSelected ? Brushes.SteelBlue : Brushes.LightGray;
Background = IsSelected ? SystemParameters.WindowGlassBrush : App.Current.Resources["GridZoneBackgroundBrush"] as SolidColorBrush;
}
public bool IsSelected
@@ -60,7 +60,7 @@ namespace FancyZonesEditor
OnSelectionChanged();
_splitter = new Rectangle
{
Fill = Brushes.DarkGray,
Fill = SystemParameters.WindowGlassBrush,
};
Body.Children.Add(_splitter);
@@ -146,13 +146,13 @@ namespace FancyZonesEditor
protected override void OnMouseEnter(MouseEventArgs e)
{
Frame.Visibility = Visibility.Visible;
_splitter.Fill = SystemParameters.WindowGlassBrush; // Active Accent color
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(MouseEventArgs e)
{
Frame.Visibility = Visibility.Collapsed;
_splitter.Fill = Brushes.Transparent;
base.OnMouseLeave(e);
}

View File

@@ -113,48 +113,86 @@ namespace FancyZonesEditor.Models
return layout;
}
public void RestoreTo(CanvasLayoutModel other)
{
other.Zones.Clear();
foreach (Int32Rect zone in Zones)
{
other.Zones.Add(zone);
}
}
private struct Zone
{
public int X { get; set; }
public int Y { get; set; }
public int Width { get; set; }
public int Height { get; set; }
}
private struct CanvasLayoutInfo
{
public int RefWidth { get; set; }
public int RefHeight { get; set; }
public Zone[] Zones { get; set; }
}
private struct CanvasLayoutJson
{
public string Uuid { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public CanvasLayoutInfo Info { get; set; }
}
// PersistData
// Implements the LayoutModel.PersistData abstract method
protected override void PersistData()
{
CanvasLayoutInfo layoutInfo = new CanvasLayoutInfo
{
RefWidth = _referenceWidth,
RefHeight = _referenceHeight,
Zones = new Zone[Zones.Count],
};
for (int i = 0; i < Zones.Count; ++i)
{
Zone zone = new Zone
{
X = Zones[i].X,
Y = Zones[i].Y,
Width = Zones[i].Width,
Height = Zones[i].Height,
};
layoutInfo.Zones[i] = zone;
}
CanvasLayoutJson jsonObj = new CanvasLayoutJson
{
Uuid = "{" + Guid.ToString().ToUpper() + "}",
Name = Name,
Type = "canvas",
Info = layoutInfo,
};
JsonSerializerOptions options = new JsonSerializerOptions
{
PropertyNamingPolicy = new DashCaseNamingPolicy(),
};
try
{
FileStream outputStream = File.Open(Settings.AppliedZoneSetTmpFile, FileMode.Create);
using (var writer = new Utf8JsonWriter(outputStream, options: default))
{
writer.WriteStartObject();
writer.WriteString("uuid", "{" + Guid.ToString().ToUpper() + "}");
writer.WriteString("name", Name);
writer.WriteString("type", "canvas");
writer.WriteStartObject("info");
writer.WriteNumber("ref-width", _referenceWidth);
writer.WriteNumber("ref-height", _referenceHeight);
writer.WriteStartArray("zones");
foreach (Int32Rect rect in Zones)
{
writer.WriteStartObject();
writer.WriteNumber("X", rect.X);
writer.WriteNumber("Y", rect.Y);
writer.WriteNumber("width", rect.Width);
writer.WriteNumber("height", rect.Height);
writer.WriteEndObject();
}
writer.WriteEndArray();
// end info object
writer.WriteEndObject();
// end root object
writer.WriteEndObject();
writer.Flush();
}
outputStream.Close();
string jsonString = JsonSerializer.Serialize(jsonObj, options);
File.WriteAllText(Settings.AppliedZoneSetTmpFile, jsonString);
}
catch (Exception ex)
{

View File

@@ -131,6 +131,12 @@ namespace FancyZonesEditor.Models
public override LayoutModel Clone()
{
GridLayoutModel layout = new GridLayoutModel(Name);
RestoreTo(layout);
return layout;
}
public void RestoreTo(GridLayoutModel layout)
{
int rows = Rows;
int cols = Columns;
@@ -163,69 +169,69 @@ namespace FancyZonesEditor.Models
}
layout.ColumnPercents = colPercents;
}
return layout;
private struct GridLayoutInfo
{
public int Rows { get; set; }
public int Columns { get; set; }
public int[] RowsPercentage { get; set; }
public int[] ColumnsPercentage { get; set; }
public int[][] CellChildMap { get; set; }
}
private struct GridLayoutJson
{
public string Uuid { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public GridLayoutInfo Info { get; set; }
}
// PersistData
// Implements the LayoutModel.PersistData abstract method
protected override void PersistData()
{
GridLayoutInfo layoutInfo = new GridLayoutInfo
{
Rows = Rows,
Columns = Columns,
RowsPercentage = RowPercents,
ColumnsPercentage = ColumnPercents,
CellChildMap = new int[Rows][],
};
for (int row = 0; row < Rows; row++)
{
layoutInfo.CellChildMap[row] = new int[Columns];
for (int col = 0; col < Columns; col++)
{
layoutInfo.CellChildMap[row][col] = CellChildMap[row, col];
}
}
GridLayoutJson jsonObj = new GridLayoutJson
{
Uuid = "{" + Guid.ToString().ToUpper() + "}",
Name = Name,
Type = "grid",
Info = layoutInfo,
};
JsonSerializerOptions options = new JsonSerializerOptions
{
PropertyNamingPolicy = new DashCaseNamingPolicy(),
};
try
{
FileStream outputStream = File.Open(Settings.AppliedZoneSetTmpFile, FileMode.Create);
using (var writer = new Utf8JsonWriter(outputStream, options: default))
{
writer.WriteStartObject();
writer.WriteString("uuid", "{" + Guid.ToString().ToUpper() + "}");
writer.WriteString("name", Name);
writer.WriteString("type", "grid");
writer.WriteStartObject("info");
writer.WriteNumber("rows", Rows);
writer.WriteNumber("columns", Columns);
writer.WriteStartArray("rows-percentage");
for (int row = 0; row < Rows; row++)
{
writer.WriteNumberValue(RowPercents[row]);
}
writer.WriteEndArray();
writer.WriteStartArray("columns-percentage");
for (int col = 0; col < Columns; col++)
{
writer.WriteNumberValue(ColumnPercents[col]);
}
writer.WriteEndArray();
writer.WriteStartArray("cell-child-map");
for (int row = 0; row < Rows; row++)
{
writer.WriteStartArray();
for (int col = 0; col < Columns; col++)
{
writer.WriteNumberValue(CellChildMap[row, col]);
}
writer.WriteEndArray();
}
writer.WriteEndArray();
// end info object
writer.WriteEndObject();
// end root object
writer.WriteEndObject();
writer.Flush();
}
outputStream.Close();
string jsonString = JsonSerializer.Serialize(jsonObj, options);
File.WriteAllText(Settings.AppliedZoneSetTmpFile, jsonString);
}
catch (Exception ex)
{

View File

@@ -135,23 +135,27 @@ namespace FancyZonesEditor.Models
}
}
private struct DeletedCustomZoneSetsWrapper
{
public List<string> DeletedCustomZoneSets { get; set; }
}
public static void SerializeDeletedCustomZoneSets()
{
DeletedCustomZoneSetsWrapper deletedLayouts = new DeletedCustomZoneSetsWrapper
{
DeletedCustomZoneSets = _deletedCustomModels,
};
JsonSerializerOptions options = new JsonSerializerOptions
{
PropertyNamingPolicy = new DashCaseNamingPolicy(),
};
try
{
FileStream outputStream = File.Open(Settings.CustomZoneSetsTmpFile, FileMode.Create);
var writer = new Utf8JsonWriter(outputStream, options: default);
writer.WriteStartObject();
writer.WriteStartArray("deleted-custom-zone-sets");
foreach (string zoneSet in _deletedCustomModels)
{
writer.WriteStringValue(zoneSet);
}
writer.WriteEndArray();
writer.WriteEndObject();
writer.Flush();
outputStream.Close();
string jsonString = JsonSerializer.Serialize(deletedLayouts, options);
File.WriteAllText(Settings.CustomZoneSetsTmpFile, jsonString);
}
catch (Exception ex)
{
@@ -258,51 +262,75 @@ namespace FancyZonesEditor.Models
Apply();
}
private struct ActiveZoneSetWrapper
{
public string Uuid { get; set; }
public string Type { get; set; }
}
private struct AppliedZoneSet
{
public string DeviceId { get; set; }
public ActiveZoneSetWrapper ActiveZoneset { get; set; }
public bool EditorShowSpacing { get; set; }
public int EditorSpacing { get; set; }
public int EditorZoneCount { get; set; }
}
public void Apply()
{
ActiveZoneSetWrapper activeZoneSet = new ActiveZoneSetWrapper
{
Uuid = "{" + Guid.ToString().ToUpper() + "}",
};
switch (Type)
{
case LayoutType.Focus:
activeZoneSet.Type = "focus";
break;
case LayoutType.Rows:
activeZoneSet.Type = "rows";
break;
case LayoutType.Columns:
activeZoneSet.Type = "columns";
break;
case LayoutType.Grid:
activeZoneSet.Type = "grid";
break;
case LayoutType.PriorityGrid:
activeZoneSet.Type = "priority-grid";
break;
case LayoutType.Custom:
activeZoneSet.Type = "custom";
break;
}
Settings settings = ((App)Application.Current).ZoneSettings;
AppliedZoneSet zoneSet = new AppliedZoneSet
{
DeviceId = Settings.UniqueKey,
ActiveZoneset = activeZoneSet,
EditorShowSpacing = settings.ShowSpacing,
EditorSpacing = settings.Spacing,
EditorZoneCount = settings.ZoneCount,
};
JsonSerializerOptions options = new JsonSerializerOptions
{
PropertyNamingPolicy = new DashCaseNamingPolicy(),
};
try
{
FileStream outputStream = File.Open(Settings.ActiveZoneSetTmpFile, FileMode.Create);
var writer = new Utf8JsonWriter(outputStream, options: default);
writer.WriteStartObject();
writer.WriteString("device-id", Settings.UniqueKey);
writer.WriteStartObject("active-zoneset");
writer.WriteString("uuid", "{" + Guid.ToString().ToUpper() + "}");
switch (Type)
{
case LayoutType.Focus:
writer.WriteString("type", "focus");
break;
case LayoutType.Rows:
writer.WriteString("type", "rows");
break;
case LayoutType.Columns:
writer.WriteString("type", "columns");
break;
case LayoutType.Grid:
writer.WriteString("type", "grid");
break;
case LayoutType.PriorityGrid:
writer.WriteString("type", "priority-grid");
break;
case LayoutType.Custom:
writer.WriteString("type", "custom");
break;
}
writer.WriteEndObject();
Settings settings = ((App)Application.Current).ZoneSettings;
writer.WriteBoolean("editor-show-spacing", settings.ShowSpacing);
writer.WriteNumber("editor-spacing", settings.Spacing);
writer.WriteNumber("editor-zone-count", settings.ZoneCount);
writer.WriteEndObject();
writer.Flush();
outputStream.Close();
string jsonString = JsonSerializer.Serialize(zoneSet, options);
File.WriteAllText(Settings.ActiveZoneSetTmpFile, jsonString);
}
catch (Exception ex)
{

View File

@@ -329,15 +329,15 @@ namespace FancyZonesEditor
_gridModel.ColumnPercents[col] = ((_multiplier * (col + 1)) / cols) - ((_multiplier * col) / cols);
}
int index = 0;
for (int col = cols - 1; col >= 0; col--)
int index = ZoneCount - 1;
for (int row = rows - 1; row >= 0; row--)
{
for (int row = rows - 1; row >= 0; row--)
for (int col = cols - 1; col >= 0; col--)
{
_gridModel.CellChildMap[row, col] = index++;
if (index == ZoneCount)
_gridModel.CellChildMap[row, col] = index--;
if (index < 0)
{
index--;
index = 0;
}
}
}
@@ -407,7 +407,8 @@ namespace FancyZonesEditor
}
inputStream.Close();
} catch (Exception ex)
}
catch (Exception ex)
{
LayoutModel.ShowExceptionMessageBox("Error parsing device info data", ex);
}

View File

@@ -0,0 +1,22 @@
// 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.Linq;
namespace FancyZonesEditor.Utils
{
public static class StringUtils
{
public static string UpperCamelCaseToDashCase(this string str)
{
// If it's single letter variable, leave it as it is
if (str.Length == 1)
{
return str;
}
return string.Concat(str.Select((x, i) => i > 0 && char.IsUpper(x) ? "-" + x.ToString() : x.ToString())).ToLower();
}
}
}

View File

@@ -12,9 +12,13 @@
#include <functional>
#include <common/common.h>
#include <lib\util.h>
#include <common/window_helpers.h>
#include <common/notifications.h>
#include <lib/util.h>
#include <unordered_set>
#include <common/notifications/fancyzones_notifications.h>
enum class DisplayChangeType
{
WorkArea,
@@ -24,6 +28,8 @@ enum class DisplayChangeType
Initialization
};
extern "C" IMAGE_DOS_HEADER __ImageBase;
namespace std
{
template<>
@@ -83,10 +89,32 @@ public:
IFACEMETHODIMP_(void)
MoveWindowsOnActiveZoneSetChange() noexcept;
IFACEMETHODIMP_(COLORREF)
GetZoneColor() noexcept
{
// Skip the leading # and convert to long
const auto color = m_settings->GetSettings()->zoneColor;
const auto tmp = std::stol(color.substr(1), nullptr, 16);
const auto nR = (tmp & 0xFF0000) >> 16;
const auto nG = (tmp & 0xFF00) >> 8;
const auto nB = (tmp & 0xFF);
return RGB(nR, nG, nB);
}
IFACEMETHODIMP_(COLORREF)
GetZoneBorderColor() noexcept
{
// Skip the leading # and convert to long
const auto color = m_settings->GetSettings()->zoneBorderColor;
const auto tmp = std::stol(color.substr(1), nullptr, 16);
const auto nR = (tmp & 0xFF0000) >> 16;
const auto nG = (tmp & 0xFF00) >> 8;
const auto nB = (tmp & 0xFF);
return RGB(nR, nG, nB);
}
IFACEMETHODIMP_(COLORREF)
GetZoneHighlightColor() noexcept
{
// Skip the leading # and convert to long
const auto color = m_settings->GetSettings().zoneHightlightColor;
const auto color = m_settings->GetSettings()->zoneHightlightColor;
const auto tmp = std::stol(color.substr(1), nullptr, 16);
const auto nR = (tmp & 0xFF0000) >> 16;
const auto nG = (tmp & 0xFF00) >> 8;
@@ -108,7 +136,13 @@ public:
IFACEMETHODIMP_(int)
GetZoneHighlightOpacity() noexcept
{
return m_settings->GetSettings().zoneHighlightOpacity;
return m_settings->GetSettings()->zoneHighlightOpacity;
}
IFACEMETHODIMP_(bool)
isMakeDraggedWindowTransparentActive() noexcept
{
return m_settings->GetSettings()->makeDraggedWindowTransparent;
}
LRESULT WndProc(HWND, UINT, WPARAM, LPARAM) noexcept;
@@ -146,9 +180,10 @@ private:
};
bool IsInterestingWindow(HWND window) noexcept;
bool IsCursorTypeIndicatingSizeEvent();
void UpdateZoneWindows() noexcept;
void MoveWindowsOnDisplayChange() noexcept;
void UpdateDragState(require_write_lock) noexcept;
void UpdateDragState(HWND window, require_write_lock) noexcept;
void CycleActiveZoneSet(DWORD vkCode) noexcept;
bool OnSnapHotkey(DWORD vkCode) noexcept;
void MoveSizeStartInternal(HWND window, HMONITOR monitor, POINT const& ptScreen, require_write_lock) noexcept;
@@ -162,6 +197,10 @@ private:
void OnEditorExitEvent() noexcept;
std::vector<std::pair<HMONITOR, RECT>> GetRawMonitorData() noexcept;
std::vector<HMONITOR> GetMonitorsSorted() noexcept;
bool MoveWindowIntoZoneByDirection(HMONITOR monitor, HWND window, DWORD vkCode, bool cycle);
const HINSTANCE m_hinstance{};
HKEY m_virtualDesktopsRegKey{ nullptr };
@@ -217,7 +256,7 @@ FancyZones::Run() noexcept
if (!m_window)
return;
RegisterHotKey(m_window, 1, m_settings->GetSettings().editorHotkey.get_modifiers(), m_settings->GetSettings().editorHotkey.get_code());
RegisterHotKey(m_window, 1, m_settings->GetSettings()->editorHotkey.get_modifiers(), m_settings->GetSettings()->editorHotkey.get_code());
VirtualDesktopInitialize();
@@ -309,7 +348,7 @@ FancyZones::VirtualDesktopInitialize() noexcept
IFACEMETHODIMP_(void)
FancyZones::WindowCreated(HWND window) noexcept
{
if (m_settings->GetSettings().appLastZone_moveWindows && IsInterestingWindow(window))
if (m_settings->GetSettings()->appLastZone_moveWindows && IsInterestingWindow(window))
{
for (const auto& [monitor, zoneWindow] : m_zoneWindowMap)
{
@@ -345,17 +384,18 @@ FancyZones::OnKeyDown(PKBDLLHOOKSTRUCT info) noexcept
bool const ctrl = GetAsyncKeyState(VK_CONTROL) & 0x8000;
if (ctrl)
{
if ((info->vkCode >= '0') && (info->vkCode <= '9'))
{
// Win+Ctrl+Number will cycle through ZoneSets
Trace::FancyZones::OnKeyDown(info->vkCode, win, ctrl, false /*inMoveSize*/);
CycleActiveZoneSet(info->vkCode);
return true;
}
// Temporarily disable Win+Ctrl+Number functionality
//if ((info->vkCode >= '0') && (info->vkCode <= '9'))
//{
// // Win+Ctrl+Number will cycle through ZoneSets
// Trace::FancyZones::OnKeyDown(info->vkCode, win, ctrl, false /*inMoveSize*/);
// CycleActiveZoneSet(info->vkCode);
// return true;
//}
}
else if ((info->vkCode == VK_RIGHT) || (info->vkCode == VK_LEFT))
{
if (m_settings->GetSettings().overrideSnapHotkeys)
if (m_settings->GetSettings()->overrideSnapHotkeys)
{
// Win+Left, Win+Right will cycle through Zones in the active ZoneSet
Trace::FancyZones::OnKeyDown(info->vkCode, win, ctrl, false /*inMoveSize*/);
@@ -363,13 +403,14 @@ FancyZones::OnKeyDown(PKBDLLHOOKSTRUCT info) noexcept
}
}
}
else if (m_inMoveSize && (info->vkCode >= '0') && (info->vkCode <= '9'))
{
// This allows you to cycle through ZoneSets while dragging a window
Trace::FancyZones::OnKeyDown(info->vkCode, win, false /*control*/, true /*inMoveSize*/);
CycleActiveZoneSet(info->vkCode);
return false;
}
// Temporarily disable Win+Ctrl+Number functionality
//else if (m_inMoveSize && (info->vkCode >= '0') && (info->vkCode <= '9'))
//{
// // This allows you to cycle through ZoneSets while dragging a window
// Trace::FancyZones::OnKeyDown(info->vkCode, win, false /*control*/, true /*inMoveSize*/);
// CycleActiveZoneSet(info->vkCode);
// return false;
//}
if (m_dragEnabled && shift)
{
return true;
@@ -397,10 +438,7 @@ void FancyZones::ToggleEditor() noexcept
HMONITOR monitor{};
HWND foregroundWindow{};
UINT dpi_x = DPIAware::DEFAULT_DPI;
UINT dpi_y = DPIAware::DEFAULT_DPI;
const bool use_cursorpos_editor_startupscreen = m_settings->GetSettings().use_cursorpos_editor_startupscreen;
const bool use_cursorpos_editor_startupscreen = m_settings->GetSettings()->use_cursorpos_editor_startupscreen;
POINT currentCursorPos{};
if (use_cursorpos_editor_startupscreen)
{
@@ -433,24 +471,14 @@ void FancyZones::ToggleEditor() noexcept
} })
.wait();
if (use_cursorpos_editor_startupscreen)
{
DPIAware::GetScreenDPIForPoint(currentCursorPos, dpi_x, dpi_y);
}
else
{
DPIAware::GetScreenDPIForWindow(foregroundWindow, dpi_x, dpi_y);
}
auto zoneWindow = iter->second;
const auto& fancyZonesData = JSONHelpers::FancyZonesDataInstance();
fancyZonesData.CustomZoneSetsToJsonFile(ZoneWindowUtils::GetCustomZoneSetsTmpPath());
const auto taskbar_x_offset = MulDiv(mi.rcWork.left - mi.rcMonitor.left, DPIAware::DEFAULT_DPI, dpi_x);
const auto taskbar_y_offset = MulDiv(mi.rcWork.top - mi.rcMonitor.top, DPIAware::DEFAULT_DPI, dpi_y);
// Do not scale window params by the dpi, that will be done in the editor - see LayoutModel.Apply
const auto taskbar_x_offset = mi.rcWork.left - mi.rcMonitor.left;
const auto taskbar_y_offset = mi.rcWork.top - mi.rcMonitor.top;
const auto x = mi.rcMonitor.left + taskbar_x_offset;
const auto y = mi.rcMonitor.top + taskbar_y_offset;
const auto width = mi.rcWork.right - mi.rcWork.left;
@@ -474,9 +502,9 @@ void FancyZones::ToggleEditor() noexcept
/*1*/ std::to_wstring(reinterpret_cast<UINT_PTR>(monitor)) + L" " +
/*2*/ editorLocation + L" " +
/*3*/ zoneWindow->WorkAreaKey() + L" " +
/*4*/ ZoneWindowUtils::GetActiveZoneSetTmpPath() + L" " +
/*5*/ ZoneWindowUtils::GetAppliedZoneSetTmpPath() + L" " +
/*6*/ ZoneWindowUtils::GetCustomZoneSetsTmpPath();
/*4*/ L"\"" + ZoneWindowUtils::GetActiveZoneSetTmpPath() + L"\" " +
/*5*/ L"\"" + ZoneWindowUtils::GetAppliedZoneSetTmpPath() + L"\" " +
/*6*/ L"\"" + ZoneWindowUtils::GetCustomZoneSetsTmpPath() + L"\"";
SHELLEXECUTEINFO sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
@@ -484,6 +512,7 @@ void FancyZones::ToggleEditor() noexcept
sei.lpParameters = params.c_str();
sei.nShow = SW_SHOWNORMAL;
ShellExecuteEx(&sei);
Trace::FancyZones::EditorLaunched(1);
// Launch the editor on a background thread
// Wait for the editor's process to exit
@@ -515,14 +544,14 @@ void FancyZones::SettingsChanged() noexcept
std::shared_lock readLock(m_lock);
// Update the hotkey
UnregisterHotKey(m_window, 1);
RegisterHotKey(m_window, 1, m_settings->GetSettings().editorHotkey.get_modifiers(), m_settings->GetSettings().editorHotkey.get_code());
RegisterHotKey(m_window, 1, m_settings->GetSettings()->editorHotkey.get_modifiers(), m_settings->GetSettings()->editorHotkey.get_code());
}
// IZoneWindowHost
IFACEMETHODIMP_(void)
FancyZones::MoveWindowsOnActiveZoneSetChange() noexcept
{
if (m_settings->GetSettings().zoneSetChange_moveWindows)
if (m_settings->GetSettings()->zoneSetChange_moveWindows)
{
MoveWindowsOnDisplayChange();
}
@@ -616,21 +645,21 @@ void FancyZones::OnDisplayChange(DisplayChangeType changeType) noexcept
if ((changeType == DisplayChangeType::WorkArea) || (changeType == DisplayChangeType::DisplayChange))
{
if (m_settings->GetSettings().displayChange_moveWindows)
if (m_settings->GetSettings()->displayChange_moveWindows)
{
MoveWindowsOnDisplayChange();
}
}
else if (changeType == DisplayChangeType::VirtualDesktop)
{
if (m_settings->GetSettings().virtualDesktopChange_moveWindows)
if (m_settings->GetSettings()->virtualDesktopChange_moveWindows)
{
MoveWindowsOnDisplayChange();
}
}
else if (changeType == DisplayChangeType::Editor)
{
if (m_settings->GetSettings().zoneSetChange_moveWindows)
if (m_settings->GetSettings()->zoneSetChange_moveWindows)
{
MoveWindowsOnDisplayChange();
}
@@ -647,7 +676,9 @@ void FancyZones::AddZoneWindow(HMONITOR monitor, PCWSTR deviceId) noexcept
JSONHelpers::FancyZonesDataInstance().SetActiveDeviceId(uniqueId);
const bool newWorkArea = IsNewWorkArea(m_currentVirtualDesktopId, monitor);
const bool flash = m_settings->GetSettings().zoneSetChange_flashZones && newWorkArea;
// "Turning FLASHING_ZONE option off"
//const bool flash = m_settings->GetSettings()->zoneSetChange_flashZones && newWorkArea;
const bool flash = false;
auto zoneWindow = MakeZoneWindow(this, m_hinstance, monitor, uniqueId, flash);
if (zoneWindow)
@@ -706,17 +737,42 @@ bool FancyZones::IsInterestingWindow(HWND window) noexcept
CharUpperBuffW(filtered.process_path.data(), (DWORD)filtered.process_path.length());
if (m_settings)
{
for (const auto& excluded : m_settings->GetSettings().excludedAppsArray)
const auto& excludedAppsArray = m_settings->GetSettings()->excludedAppsArray;
if (find_app_name_in_path(filtered.process_path, excludedAppsArray))
{
if (filtered.process_path.find(excluded) != std::wstring::npos)
{
return false;
}
return false;
}
}
return true;
}
bool FancyZones::IsCursorTypeIndicatingSizeEvent()
{
CURSORINFO cursorInfo = { 0 };
cursorInfo.cbSize = sizeof(cursorInfo);
if (::GetCursorInfo(&cursorInfo))
{
if (::LoadCursor(NULL, IDC_SIZENS) == cursorInfo.hCursor)
{
return true;
}
if (::LoadCursor(NULL, IDC_SIZEWE) == cursorInfo.hCursor)
{
return true;
}
if (::LoadCursor(NULL, IDC_SIZENESW) == cursorInfo.hCursor)
{
return true;
}
if (::LoadCursor(NULL, IDC_SIZENWSE) == cursorInfo.hCursor)
{
return true;
}
}
return false;
}
void FancyZones::UpdateZoneWindows() noexcept
{
auto callback = [](HMONITOR monitor, HDC, RECT*, LPARAM data) -> BOOL {
@@ -774,7 +830,7 @@ void FancyZones::MoveWindowsOnDisplayChange() noexcept
EnumWindows(callback, reinterpret_cast<LPARAM>(this));
}
void FancyZones::UpdateDragState(require_write_lock) noexcept
void FancyZones::UpdateDragState(HWND window, require_write_lock) noexcept
{
const bool shift = GetAsyncKeyState(VK_SHIFT) & 0x8000;
const bool mouseL = GetAsyncKeyState(VK_LBUTTON) & 0x8000;
@@ -795,7 +851,7 @@ void FancyZones::UpdateDragState(require_write_lock) noexcept
mouse |= mouseR;
}
if (m_settings->GetSettings().shiftDrag)
if (m_settings->GetSettings()->shiftDrag)
{
m_dragEnabled = (shift | mouse);
}
@@ -803,6 +859,23 @@ void FancyZones::UpdateDragState(require_write_lock) noexcept
{
m_dragEnabled = !(shift | mouse);
}
const bool windowElevated = IsProcessOfWindowElevated(window);
static const bool meElevated = is_process_elevated();
static bool warning_shown = false;
if (windowElevated && !meElevated)
{
m_dragEnabled = false;
if (!warning_shown && !is_cant_drag_elevated_warning_disabled())
{
std::vector<notifications::action_t> actions = {
notifications::link_button{ GET_RESOURCE_STRING(IDS_CANT_DRAG_ELEVATED_LEARN_MORE), L"https://aka.ms/powertoysDetectedElevatedHelp" },
notifications::link_button{ GET_RESOURCE_STRING(IDS_CANT_DRAG_ELEVATED_DIALOG_DONT_SHOW_AGAIN), L"powertoys://cant_drag_elevated_disable/" }
};
notifications::show_toast_with_activations(GET_RESOURCE_STRING(IDS_CANT_DRAG_ELEVATED), {}, std::move(actions));
warning_shown = true;
}
}
}
void FancyZones::CycleActiveZoneSet(DWORD vkCode) noexcept
@@ -830,17 +903,43 @@ bool FancyZones::OnSnapHotkey(DWORD vkCode) noexcept
auto window = GetForegroundWindow();
if (IsInterestingWindow(window))
{
const HMONITOR monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL);
if (monitor)
const HMONITOR current = MonitorFromWindow(window, MONITOR_DEFAULTTONULL);
if (current)
{
std::shared_lock readLock(m_lock);
auto iter = m_zoneWindowMap.find(monitor);
if (iter != m_zoneWindowMap.end())
std::vector<HMONITOR> monitorInfo = GetMonitorsSorted();
if (monitorInfo.size() > 1)
{
const auto& zoneWindowPtr = iter->second;
zoneWindowPtr->MoveWindowIntoZoneByDirection(window, vkCode);
return true;
// Multi monitor environment.
auto currMonitorInfo = std::find(std::begin(monitorInfo), std::end(monitorInfo), current);
do
{
if (MoveWindowIntoZoneByDirection(*currMonitorInfo, window, vkCode, false /* cycle through zones */))
{
return true;
}
// We iterated through all zones in current monitor zone layout, move on to next one (or previous depending on direction).
if (vkCode == VK_RIGHT)
{
currMonitorInfo = std::next(currMonitorInfo);
if (currMonitorInfo == std::end(monitorInfo))
{
currMonitorInfo = std::begin(monitorInfo);
}
}
else if (vkCode == VK_LEFT)
{
if (currMonitorInfo == std::begin(monitorInfo))
{
currMonitorInfo = std::end(monitorInfo);
}
currMonitorInfo = std::prev(currMonitorInfo);
}
} while (*currMonitorInfo != current);
}
else
{
// Single monitor environment.
return MoveWindowIntoZoneByDirection(current, window, vkCode, true /* cycle through zones */);
}
}
}
@@ -849,23 +948,10 @@ bool FancyZones::OnSnapHotkey(DWORD vkCode) noexcept
void FancyZones::MoveSizeStartInternal(HWND window, HMONITOR monitor, POINT const& ptScreen, require_write_lock writeLock) noexcept
{
// Only enter move/size if the cursor is inside the window rect by a certain padding.
// This prevents resize from triggering zones.
RECT windowRect{};
::GetWindowRect(window, &windowRect);
const auto padding_x = 8;
const auto padding_y = 6;
windowRect.top += padding_y;
windowRect.left += padding_x;
windowRect.right -= padding_x;
windowRect.bottom -= padding_y;
if (PtInRect(&windowRect, ptScreen) == FALSE)
if (IsCursorTypeIndicatingSizeEvent())
{
return;
}
m_inMoveSize = true;
auto iter = m_zoneWindowMap.find(monitor);
@@ -877,17 +963,37 @@ void FancyZones::MoveSizeStartInternal(HWND window, HMONITOR monitor, POINT cons
m_windowMoveSize = window;
// This updates m_dragEnabled depending on if the shift key is being held down.
UpdateDragState(writeLock);
UpdateDragState(window, writeLock);
if (m_dragEnabled)
{
m_zoneWindowMoveSize = iter->second;
m_zoneWindowMoveSize->MoveSizeEnter(window, m_dragEnabled);
if (m_settings->GetSettings()->showZonesOnAllMonitors)
{
for (auto [keyMonitor, zoneWindow] : m_zoneWindowMap)
{
// Skip calling ShowZoneWindow for iter->second (m_zoneWindowMoveSize) since it
// was already called in MoveSizeEnter
const bool moveSizeEnterCalled = zoneWindow == m_zoneWindowMoveSize;
if (zoneWindow && !moveSizeEnterCalled)
{
zoneWindow->ShowZoneWindow();
}
}
}
}
else if (m_zoneWindowMoveSize)
{
m_zoneWindowMoveSize->MoveSizeCancel();
m_zoneWindowMoveSize->RestoreOrginalTransparency();
m_zoneWindowMoveSize = nullptr;
for (auto [keyMonitor, zoneWindow] : m_zoneWindowMap)
{
if (zoneWindow)
{
zoneWindow->HideZoneWindow();
}
}
}
}
@@ -924,6 +1030,15 @@ void FancyZones::MoveSizeEndInternal(HWND window, POINT const& ptScreen, require
}
}
}
// Also, hide all windows (regardless of settings)
for (auto [keyMonitor, zoneWindow] : m_zoneWindowMap)
{
if (zoneWindow)
{
zoneWindow->HideZoneWindow();
}
}
}
void FancyZones::MoveSizeUpdateInternal(HMONITOR monitor, POINT const& ptScreen, require_write_lock writeLock) noexcept
@@ -931,16 +1046,24 @@ void FancyZones::MoveSizeUpdateInternal(HMONITOR monitor, POINT const& ptScreen,
if (m_inMoveSize)
{
// This updates m_dragEnabled depending on if the shift key is being held down.
UpdateDragState(writeLock);
UpdateDragState(m_windowMoveSize, writeLock);
if (m_zoneWindowMoveSize)
{
// Update the ZoneWindow already handling move/size
if (!m_dragEnabled)
{
// Drag got disabled, tell it to cancel and clear out m_zoneWindowMoveSize
auto zoneWindow = std::move(m_zoneWindowMoveSize);
zoneWindow->MoveSizeCancel();
// Drag got disabled, tell it to cancel and hide all windows
m_zoneWindowMoveSize = nullptr;
for (auto [keyMonitor, zoneWindow] : m_zoneWindowMap)
{
if (zoneWindow)
{
zoneWindow->RestoreOrginalTransparency();
zoneWindow->HideZoneWindow();
}
}
}
else
{
@@ -950,12 +1073,20 @@ void FancyZones::MoveSizeUpdateInternal(HMONITOR monitor, POINT const& ptScreen,
if (iter->second != m_zoneWindowMoveSize)
{
// The drag has moved to a different monitor.
auto const isDragEnabled = m_zoneWindowMoveSize->IsDragEnabled();
m_zoneWindowMoveSize->MoveSizeCancel();
m_zoneWindowMoveSize->RestoreOrginalTransparency();
if (!m_settings->GetSettings()->showZonesOnAllMonitors)
{
m_zoneWindowMoveSize->HideZoneWindow();
}
m_zoneWindowMoveSize = iter->second;
m_zoneWindowMoveSize->MoveSizeEnter(m_windowMoveSize, isDragEnabled);
m_zoneWindowMoveSize->MoveSizeEnter(m_windowMoveSize, m_zoneWindowMoveSize->IsDragEnabled());
}
for (auto [keyMonitor, zoneWindow] : m_zoneWindowMap)
{
zoneWindow->MoveSizeUpdate(ptScreen, m_dragEnabled);
}
m_zoneWindowMoveSize->MoveSizeUpdate(ptScreen, m_dragEnabled);
}
}
}
@@ -1075,6 +1206,46 @@ void FancyZones::OnEditorExitEvent() noexcept
JSONHelpers::FancyZonesDataInstance().SaveFancyZonesData();
}
std::vector<HMONITOR> FancyZones::GetMonitorsSorted() noexcept
{
std::shared_lock readLock(m_lock);
auto monitorInfo = GetRawMonitorData();
OrderMonitors(monitorInfo);
std::vector<HMONITOR> output;
std::transform(std::begin(monitorInfo), std::end(monitorInfo), std::back_inserter(output), [](const auto& info) { return info.first; });
return output;
}
std::vector<std::pair<HMONITOR, RECT>> FancyZones::GetRawMonitorData() noexcept
{
std::shared_lock readLock(m_lock);
std::vector<std::pair<HMONITOR, RECT>> monitorInfo;
for (const auto& [monitor, window] : m_zoneWindowMap)
{
if (window->ActiveZoneSet() != nullptr)
{
MONITORINFOEX mi;
mi.cbSize = sizeof(mi);
GetMonitorInfo(monitor, &mi);
monitorInfo.push_back({ monitor, mi.rcMonitor });
}
}
return monitorInfo;
}
bool FancyZones::MoveWindowIntoZoneByDirection(HMONITOR monitor, HWND window, DWORD vkCode, bool cycle)
{
auto iter = m_zoneWindowMap.find(monitor);
if (iter != std::end(m_zoneWindowMap))
{
const auto& zoneWindowPtr = iter->second;
return zoneWindowPtr->MoveWindowIntoZoneByDirection(window, vkCode, cycle);
}
return false;
}
winrt::com_ptr<IFancyZones> MakeFancyZones(HINSTANCE hinstance, const winrt::com_ptr<IFancyZonesSettings>& settings) noexcept
{
if (!settings)
@@ -1083,4 +1254,4 @@ winrt::com_ptr<IFancyZones> MakeFancyZones(HINSTANCE hinstance, const winrt::com
}
return winrt::make_self<FancyZones>(hinstance, settings);
}
}

View File

@@ -1,106 +1,118 @@
#pragma once
interface IZoneWindow;
interface IFancyZonesSettings;
interface IZoneSet;
interface __declspec(uuid("{50D3F0F5-736E-4186-BDF4-3D6BEE150C3A}")) IFancyZones : public IUnknown
{
/**
* Start and initialize FancyZones.
*/
IFACEMETHOD_(void, Run)() = 0;
/**
* Stop FancyZones and do the clean up.
*/
IFACEMETHOD_(void, Destroy)() = 0;
};
/**
* Core FancyZones functionality.
*/
interface __declspec(uuid("{2CB37E8F-87E6-4AEC-B4B2-E0FDC873343F}")) IFancyZonesCallback : public IUnknown
{
/**
* @returns Boolean indicating whether a move/size operation is currently active.
*/
IFACEMETHOD_(bool, InMoveSize)() = 0;
/**
* A window is being moved or resized. Track down window position and give zone layout
* hints if dragging functionality is enabled.
*
* @param window Handle of window being moved or resized.
* @param monitor Handle of monitor on which windows is moving / resizing.
* @param ptScreen Cursor coordinates.
*/
IFACEMETHOD_(void, MoveSizeStart)(HWND window, HMONITOR monitor, POINT const& ptScreen) = 0;
/**
* A window has changed location, shape, or size. Track down window position and give zone layout
* hints if dragging functionality is enabled.
*
* @param monitor Handle of monitor on which windows is moving / resizing.
* @param ptScreen Cursor coordinates.
*/
IFACEMETHOD_(void, MoveSizeUpdate)(HMONITOR monitor, POINT const& ptScreen) = 0;
/**
* The movement or resizing of a window has finished. Assign window to the zone if it
* is dropped within zone borders.
*
* @param window Handle of window being moved or resized.
* @param ptScreen Cursor coordinates where window is droped.
*/
IFACEMETHOD_(void, MoveSizeEnd)(HWND window, POINT const& ptScreen) = 0;
/**
* Inform FancyZones that user has switched between virtual desktops.
*/
IFACEMETHOD_(void, VirtualDesktopChanged)() = 0;
/**
* Inform FancyZones that new window is created. FancyZones will try to assign it to the
* zone insde active zone layout (if information about last zone, in which window was located
* before being closed, is available).
*
* @param window Handle of newly created window.
*/
IFACEMETHOD_(void, WindowCreated)(HWND window) = 0;
/**
* Process keyboard event.
*
* @param info Information about low level keyboard event.
* @returns Boolean indicating if this event should be passed on further to other applications
* in event chain, or should it be suppressed.
*/
IFACEMETHOD_(bool, OnKeyDown)(PKBDLLHOOKSTRUCT info) = 0;
/**
* Toggle FancyZones editor application.
*/
IFACEMETHOD_(void, ToggleEditor)() = 0;
/**
* Callback triggered when user changes FancyZones settings.
*/
IFACEMETHOD_(void, SettingsChanged)() = 0;
};
/**
* Helper functions used by each ZoneWindow (representing work area).
*/
interface __declspec(uuid("{5C8D99D6-34B2-4F4A-A8E5-7483F6869775}")) IZoneWindowHost : public IUnknown
{
/**
* Assign window to appropriate zone inside new zone layout.
*/
IFACEMETHOD_(void, MoveWindowsOnActiveZoneSetChange)() = 0;
/**
* @returns Color used to highlight zone while giving zone layout hints.
*/
IFACEMETHOD_(COLORREF, GetZoneHighlightColor)() = 0;
/**
* @returns ZoneWindow (representing work area) currently being processed.
*/
IFACEMETHOD_(IZoneWindow*, GetParentZoneWindow) (HMONITOR monitor) = 0;
/**
* @returns Integer in range [0, 100] indicating opacity of highlited zone (while giving zone layout hints).
*/
IFACEMETHOD_(int, GetZoneHighlightOpacity)() = 0;
};
winrt::com_ptr<IFancyZones> MakeFancyZones(HINSTANCE hinstance, const winrt::com_ptr<IFancyZonesSettings>& settings) noexcept;
#pragma once
interface IZoneWindow;
interface IFancyZonesSettings;
interface IZoneSet;
interface __declspec(uuid("{50D3F0F5-736E-4186-BDF4-3D6BEE150C3A}")) IFancyZones : public IUnknown
{
/**
* Start and initialize FancyZones.
*/
IFACEMETHOD_(void, Run)() = 0;
/**
* Stop FancyZones and do the clean up.
*/
IFACEMETHOD_(void, Destroy)() = 0;
};
/**
* Core FancyZones functionality.
*/
interface __declspec(uuid("{2CB37E8F-87E6-4AEC-B4B2-E0FDC873343F}")) IFancyZonesCallback : public IUnknown
{
/**
* @returns Boolean indicating whether a move/size operation is currently active.
*/
IFACEMETHOD_(bool, InMoveSize)() = 0;
/**
* A window is being moved or resized. Track down window position and give zone layout
* hints if dragging functionality is enabled.
*
* @param window Handle of window being moved or resized.
* @param monitor Handle of monitor on which windows is moving / resizing.
* @param ptScreen Cursor coordinates.
*/
IFACEMETHOD_(void, MoveSizeStart)(HWND window, HMONITOR monitor, POINT const& ptScreen) = 0;
/**
* A window has changed location, shape, or size. Track down window position and give zone layout
* hints if dragging functionality is enabled.
*
* @param monitor Handle of monitor on which windows is moving / resizing.
* @param ptScreen Cursor coordinates.
*/
IFACEMETHOD_(void, MoveSizeUpdate)(HMONITOR monitor, POINT const& ptScreen) = 0;
/**
* The movement or resizing of a window has finished. Assign window to the zone if it
* is dropped within zone borders.
*
* @param window Handle of window being moved or resized.
* @param ptScreen Cursor coordinates where window is droped.
*/
IFACEMETHOD_(void, MoveSizeEnd)(HWND window, POINT const& ptScreen) = 0;
/**
* Inform FancyZones that user has switched between virtual desktops.
*/
IFACEMETHOD_(void, VirtualDesktopChanged)() = 0;
/**
* Inform FancyZones that new window is created. FancyZones will try to assign it to the
* zone insde active zone layout (if information about last zone, in which window was located
* before being closed, is available).
*
* @param window Handle of newly created window.
*/
IFACEMETHOD_(void, WindowCreated)(HWND window) = 0;
/**
* Process keyboard event.
*
* @param info Information about low level keyboard event.
* @returns Boolean indicating if this event should be passed on further to other applications
* in event chain, or should it be suppressed.
*/
IFACEMETHOD_(bool, OnKeyDown)(PKBDLLHOOKSTRUCT info) = 0;
/**
* Toggle FancyZones editor application.
*/
IFACEMETHOD_(void, ToggleEditor)() = 0;
/**
* Callback triggered when user changes FancyZones settings.
*/
IFACEMETHOD_(void, SettingsChanged)() = 0;
};
/**
* Helper functions used by each ZoneWindow (representing work area).
*/
interface __declspec(uuid("{5C8D99D6-34B2-4F4A-A8E5-7483F6869775}")) IZoneWindowHost : public IUnknown
{
/**
* Assign window to appropriate zone inside new zone layout.
*/
IFACEMETHOD_(void, MoveWindowsOnActiveZoneSetChange)() = 0;
/**
* @returns Basic zone color.
*/
IFACEMETHOD_(COLORREF, GetZoneColor)() = 0;
/**
* @returns Zone border color.
*/
IFACEMETHOD_(COLORREF, GetZoneBorderColor)() = 0;
/**
* @returns Color used to highlight zone while giving zone layout hints.
*/
IFACEMETHOD_(COLORREF, GetZoneHighlightColor)() = 0;
/**
* @returns ZoneWindow (representing work area) currently being processed.
*/
IFACEMETHOD_(IZoneWindow*, GetParentZoneWindow) (HMONITOR monitor) = 0;
/**
* @returns Integer in range [0, 100] indicating opacity of highlited zone (while giving zone layout hints).
*/
IFACEMETHOD_(int, GetZoneHighlightOpacity)() = 0;
/**
* @returns Bool indicating if dragged window should be transparrent
*/
IFACEMETHOD_(bool, isMakeDraggedWindowTransparentActive) () = 0;
};
winrt::com_ptr<IFancyZones> MakeFancyZones(HINSTANCE hinstance, const winrt::com_ptr<IFancyZonesSettings>& settings) noexcept;

View File

@@ -46,9 +46,13 @@
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>

View File

@@ -10,6 +10,7 @@
#include <filesystem>
#include <fstream>
#include <regex>
#include <sstream>
namespace
{
@@ -46,6 +47,84 @@ namespace
namespace JSONHelpers
{
bool isValidGuid(const std::wstring& str)
{
GUID id;
return SUCCEEDED_LOG(CLSIDFromString(str.c_str(), &id));
}
bool isValidDeviceId(const std::wstring& str)
{
std::wstring monitorName;
std::wstring temp;
std::vector<std::wstring> parts;
std::wstringstream wss(str);
/*
Important fix for device info that contains a '_' in the name:
1. first search for '#'
2. Then split the remaining string by '_'
*/
// Step 1: parse the name until the #, then to the '_'
if (str.find(L'#') != std::string::npos)
{
std::getline(wss, temp, L'#');
monitorName = temp;
if (!std::getline(wss, temp, L'_'))
{
return false;
}
monitorName += L"#" + temp;
parts.push_back(monitorName);
}
// Step 2: parse the rest of the id
while (std::getline(wss, temp, L'_'))
{
parts.push_back(temp);
}
if (parts.size() != 4)
{
return false;
}
/*
Refer to ZoneWindowUtils::GenerateUniqueId parts contain:
1. monitor id [string]
2. width of device [int]
3. height of device [int]
4. virtual desktop id (GUID) [string]
*/
try
{
//check if resolution contain only digits
for (const auto& c : parts[1])
{
std::stoi(std::wstring(&c));
}
for (const auto& c : parts[2])
{
std::stoi(std::wstring(&c));
}
}
catch (const std::exception&)
{
return false;
}
if (!isValidGuid(parts[3]) || parts[0].empty())
{
return false;
}
return true;
}
json::JsonArray NumVecToJsonArray(const std::vector<int>& vec)
{
json::JsonArray arr;
@@ -525,9 +604,6 @@ namespace JSONHelpers
if (!std::filesystem::exists(jsonFilePath))
{
TmpMigrateAppliedZoneSetsFromRegistry();
// Custom zone sets have to be migrated after applied zone sets!
MigrateCustomZoneSetsFromRegistry();
SaveFancyZonesData();
@@ -560,56 +636,6 @@ namespace JSONHelpers
json::to_file(jsonFilePath, root);
}
void FancyZonesData::TmpMigrateAppliedZoneSetsFromRegistry()
{
std::wregex ex(L"^[0-9]{3,4}_[0-9]{3,4}$");
std::scoped_lock lock{ dataLock };
wchar_t key[256];
StringCchPrintf(key, ARRAYSIZE(key), L"%s", RegistryHelpers::REG_SETTINGS);
HKEY hkey;
if (RegOpenKeyExW(HKEY_CURRENT_USER, key, 0, KEY_ALL_ACCESS, &hkey) == ERROR_SUCCESS)
{
wchar_t resolutionKey[256]{};
DWORD resolutionKeyLength = ARRAYSIZE(resolutionKey);
DWORD i = 0;
while (RegEnumKeyW(hkey, i++, resolutionKey, resolutionKeyLength) == ERROR_SUCCESS)
{
std::wstring resolution{ resolutionKey };
wchar_t appliedZoneSetskey[256];
StringCchPrintf(appliedZoneSetskey, ARRAYSIZE(appliedZoneSetskey), L"%s\\%s", RegistryHelpers::REG_SETTINGS, resolutionKey);
HKEY appliedZoneSetsHkey;
if (std::regex_match(resolution, ex) && RegOpenKeyExW(HKEY_CURRENT_USER, appliedZoneSetskey, 0, KEY_ALL_ACCESS, &appliedZoneSetsHkey) == ERROR_SUCCESS)
{
ZoneSetPersistedDataOLD data;
DWORD dataSize = sizeof(data);
wchar_t value[256]{};
DWORD valueLength = ARRAYSIZE(value);
DWORD i = 0;
while (RegEnumValueW(appliedZoneSetsHkey, i++, value, &valueLength, nullptr, nullptr, reinterpret_cast<BYTE*>(&data), &dataSize) == ERROR_SUCCESS)
{
ZoneSetData appliedZoneSetData;
appliedZoneSetData.type = TypeFromLayoutId(data.LayoutId);
if (appliedZoneSetData.type != ZoneSetLayoutType::Custom)
{
appliedZoneSetData.uuid = std::wstring{ value };
}
else
{
// uuid is changed later to actual uuid when migrating custom zone sets
appliedZoneSetData.uuid = std::to_wstring(data.LayoutId);
}
appliedZoneSetsMap[value] = appliedZoneSetData;
dataSize = sizeof(data);
valueLength = ARRAYSIZE(value);
}
}
resolutionKeyLength = ARRAYSIZE(resolutionKey);
}
}
}
void FancyZonesData::MigrateCustomZoneSetsFromRegistry()
{
std::scoped_lock lock{ dataLock };
@@ -630,29 +656,19 @@ namespace JSONHelpers
zoneSetData.type = static_cast<CustomLayoutType>(data[2]);
// int version = data[0] * 256 + data[1]; - Not used anymore
std::wstring uuid = std::to_wstring(data[3] * 256 + data[4]);
auto it = std::find_if(appliedZoneSetsMap.begin(), appliedZoneSetsMap.end(), [&uuid](std::pair<std::wstring, ZoneSetData> zoneSetMap) {
return zoneSetMap.second.uuid.compare(uuid) == 0;
});
GUID guid;
auto result = CoCreateGuid(&guid);
if (result != S_OK)
{
continue;
}
wil::unique_cotaskmem_string guidString;
if (!SUCCEEDED_LOG(StringFromCLSID(guid, &guidString)))
{
continue;
}
if (it != appliedZoneSetsMap.end())
{
it->second.uuid = uuid = it->first;
}
else
{
GUID guid;
auto result = CoCreateGuid(&guid);
if (result != S_OK)
{
return;
}
wil::unique_cotaskmem_string guidString;
if (SUCCEEDED_LOG(StringFromCLSID(guid, &guidString)))
{
it->second.uuid = uuid = guidString.get();
}
}
std::wstring uuid = guidString.get();
switch (zoneSetData.type)
{
@@ -660,14 +676,14 @@ namespace JSONHelpers
int j = 5;
GridLayoutInfo zoneSetInfo(GridLayoutInfo::Minimal{ .rows = data[j++], .columns = data[j++] });
for (int row = 0; row < zoneSetInfo.rows(); row++)
for (int row = 0; row < zoneSetInfo.rows(); row++, j+=2)
{
zoneSetInfo.rowsPercents()[row] = data[j++] * 256 + data[j++];
zoneSetInfo.rowsPercents()[row] = data[j] * 256 + data[j+1];
}
for (int col = 0; col < zoneSetInfo.columns(); col++)
for (int col = 0; col < zoneSetInfo.columns(); col++, j+=2)
{
zoneSetInfo.columnsPercents()[col] = data[j++] * 256 + data[j++];
zoneSetInfo.columnsPercents()[col] = data[j] * 256 + data[j+1];
}
for (int row = 0; row < zoneSetInfo.rows(); row++)
@@ -733,10 +749,14 @@ namespace JSONHelpers
try
{
ZoneSetData zoneSetData;
zoneSetData.uuid = zoneSet.GetNamedString(L"uuid");
zoneSetData.type = TypeFromString(std::wstring{ zoneSet.GetNamedString(L"type") });
if (!isValidGuid(zoneSetData.uuid))
{
return std::nullopt;
}
return zoneSetData;
}
catch (const winrt::hresult_error&)
@@ -768,6 +788,11 @@ namespace JSONHelpers
result.data.deviceId = zoneSet.GetNamedString(L"device-id");
result.data.zoneSetUuid = zoneSet.GetNamedString(L"zoneset-uuid");
if (!isValidGuid(result.data.zoneSetUuid) || !isValidDeviceId(result.data.deviceId))
{
return std::nullopt;
}
return result;
}
catch (const winrt::hresult_error&)
@@ -796,6 +821,10 @@ namespace JSONHelpers
DeviceInfoJSON result;
result.deviceId = device.GetNamedString(L"device-id");
if (!isValidDeviceId(result.deviceId))
{
return std::nullopt;
}
if (auto zoneSet = ZoneSetData::FromJson(device.GetNamedObject(L"active-zoneset")); zoneSet.has_value())
{
@@ -988,6 +1017,11 @@ namespace JSONHelpers
CustomZoneSetJSON result;
result.uuid = customZoneSet.GetNamedString(L"uuid");
if (!isValidGuid(result.uuid))
{
return std::nullopt;
}
result.data.name = customZoneSet.GetNamedString(L"name");
json::JsonObject infoJson = customZoneSet.GetNamedObject(L"info");

View File

@@ -16,6 +16,11 @@ namespace JSONHelpers
{
constexpr int MAX_ZONE_COUNT = 50;
#if defined(UNIT_TESTS)
bool isValidGuid(const std::wstring& str);
bool isValidDeviceId(const std::wstring& str);
#endif
enum class ZoneSetLayoutType : int
{
Blank = -1,
@@ -202,7 +207,6 @@ namespace JSONHelpers
#if defined(UNIT_TESTS)
inline void clear_data()
{
appliedZoneSetsMap.clear();
appZoneHistoryMap.clear();
deviceInfoMap.clear();
customZoneSetsMap.clear();
@@ -249,10 +253,8 @@ namespace JSONHelpers
void SaveFancyZonesData() const;
private:
void TmpMigrateAppliedZoneSetsFromRegistry();
void MigrateCustomZoneSetsFromRegistry();
std::unordered_map<std::wstring, ZoneSetData> appliedZoneSetsMap{};
std::unordered_map<std::wstring, AppZoneHistoryData> appZoneHistoryMap{};
std::unordered_map<std::wstring, DeviceInfoData> deviceInfoMap{};
std::unordered_map<std::wstring, CustomZoneSetData> customZoneSetsMap{};

View File

@@ -19,7 +19,7 @@ public:
IFACEMETHODIMP_(bool) GetConfig(_Out_ PWSTR buffer, _Out_ int *buffer_sizeg) noexcept;
IFACEMETHODIMP_(void) SetConfig(PCWSTR config) noexcept;
IFACEMETHODIMP_(void) CallCustomAction(PCWSTR action) noexcept;
IFACEMETHODIMP_(Settings) GetSettings() noexcept { return m_settings; }
IFACEMETHODIMP_(const Settings*) GetSettings() const noexcept { return &m_settings; }
private:
void LoadSettings(PCWSTR config, bool fromFile) noexcept;
@@ -36,17 +36,22 @@ private:
PCWSTR name;
bool* value;
int resourceId;
} m_configBools[8] = {
} m_configBools[9 /* 10 */] = { // "Turning FLASHING_ZONE option off"
{ L"fancyzones_shiftDrag", &m_settings.shiftDrag, IDS_SETTING_DESCRIPTION_SHIFTDRAG },
{ L"fancyzones_overrideSnapHotkeys", &m_settings.overrideSnapHotkeys, IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS },
{ L"fancyzones_zoneSetChange_flashZones", &m_settings.zoneSetChange_flashZones, IDS_SETTING_DESCRIPTION_ZONESETCHANGE_FLASHZONES },
// "Turning FLASHING_ZONE option off"
//{ L"fancyzones_zoneSetChange_flashZones", &m_settings.zoneSetChange_flashZones, IDS_SETTING_DESCRIPTION_ZONESETCHANGE_FLASHZONES },
{ L"fancyzones_displayChange_moveWindows", &m_settings.displayChange_moveWindows, IDS_SETTING_DESCRIPTION_DISPLAYCHANGE_MOVEWINDOWS },
{ L"fancyzones_zoneSetChange_moveWindows", &m_settings.zoneSetChange_moveWindows, IDS_SETTING_DESCRIPTION_ZONESETCHANGE_MOVEWINDOWS },
{ L"fancyzones_virtualDesktopChange_moveWindows", &m_settings.virtualDesktopChange_moveWindows, IDS_SETTING_DESCRIPTION_VIRTUALDESKTOPCHANGE_MOVEWINDOWS },
{ L"fancyzones_appLastZone_moveWindows", &m_settings.appLastZone_moveWindows, IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS },
{ L"use_cursorpos_editor_startupscreen", &m_settings.use_cursorpos_editor_startupscreen, IDS_SETTING_DESCRIPTION_USE_CURSORPOS_EDITOR_STARTUPSCREEN },
{ L"fancyzones_show_on_all_monitors", &m_settings.showZonesOnAllMonitors, IDS_SETTING_DESCRIPTION_SHOW_FANCY_ZONES_ON_ALL_MONITORS},
{ L"fancyzones_makeDraggedWindowTransparent", &m_settings.makeDraggedWindowTransparent, IDS_SETTING_DESCRIPTION_MAKE_DRAGGED_WINDOW_TRANSPARENT},
};
const std::wstring m_zoneColorName = L"fancyzones_zoneColor";
const std::wstring m_zoneBorderColorName = L"fancyzones_zoneBorderColor";
const std::wstring m_zoneHiglightName = L"fancyzones_zoneHighlightColor";
const std::wstring m_editorHotkeyName = L"fancyzones_editor_hotkey";
const std::wstring m_excludedAppsName = L"fancyzones_excluded_apps";
@@ -78,8 +83,12 @@ IFACEMETHODIMP_(bool) FancyZonesSettings::GetConfig(_Out_ PWSTR buffer, _Out_ in
settings.add_bool_toogle(setting.name, setting.resourceId, *setting.value);
}
settings.add_int_spinner(m_zoneHighlightOpacity, IDS_SETTINGS_HIGHLIGHT_OPACITY, m_settings.zoneHighlightOpacity, 0, 100, 1);
settings.add_color_picker(m_zoneHiglightName, IDS_SETTING_DESCRIPTION_ZONEHIGHLIGHTCOLOR, m_settings.zoneHightlightColor);
settings.add_color_picker(m_zoneColorName, IDS_SETTING_DESCRIPTION_ZONECOLOR, m_settings.zoneColor);
settings.add_color_picker(m_zoneBorderColorName, IDS_SETTING_DESCRIPTION_ZONE_BORDER_COLOR, m_settings.zoneBorderColor);
settings.add_int_spinner(m_zoneHighlightOpacity, IDS_SETTINGS_HIGHLIGHT_OPACITY, m_settings.zoneHighlightOpacity, 0, 100, 1);
settings.add_multiline_string(m_excludedAppsName, IDS_SETTING_EXCLCUDED_APPS_DESCRIPTION, m_settings.excludedApps);
return settings.serialize_to_buffer(buffer, buffer_size);
@@ -124,6 +133,16 @@ void FancyZonesSettings::LoadSettings(PCWSTR config, bool fromFile) noexcept try
}
}
if (auto val = values.get_string_value(m_zoneColorName))
{
m_settings.zoneColor = std::move(*val);
}
if (auto val = values.get_string_value(m_zoneBorderColorName))
{
m_settings.zoneBorderColor = std::move(*val);
}
if (auto val = values.get_string_value(m_zoneHiglightName))
{
m_settings.zoneHightlightColor = std::move(*val);
@@ -173,6 +192,8 @@ void FancyZonesSettings::SaveSettings() noexcept try
values.add_property(setting.name, *setting.value);
}
values.add_property(m_zoneColorName, m_settings.zoneColor);
values.add_property(m_zoneBorderColorName, m_settings.zoneBorderColor);
values.add_property(m_zoneHiglightName, m_settings.zoneHightlightColor);
values.add_property(m_zoneHighlightOpacity, m_settings.zoneHighlightOpacity);
values.add_property(m_editorHotkeyName, m_settings.editorHotkey.get_json());

View File

@@ -14,8 +14,12 @@ struct Settings
bool overrideSnapHotkeys = false;
bool appLastZone_moveWindows = false;
bool use_cursorpos_editor_startupscreen = true;
std::wstring zoneHightlightColor = L"#0078D7";
int zoneHighlightOpacity = 90;
bool showZonesOnAllMonitors = false;
bool makeDraggedWindowTransparent = true;
std::wstring zoneColor = L"#F5FCFF";
std::wstring zoneBorderColor = L"#FFFFFF";
std::wstring zoneHightlightColor = L"#008CFF";
int zoneHighlightOpacity = 50;
PowerToysSettings::HotkeyObject editorHotkey = PowerToysSettings::HotkeyObject::from_settings(true, false, false, false, VK_OEM_3);
std::wstring excludedApps = L"";
std::vector<std::wstring> excludedAppsArray;
@@ -28,7 +32,7 @@ interface __declspec(uuid("{BA4E77C4-6F44-4C5D-93D3-CBDE880495C2}")) IFancyZones
IFACEMETHOD_(bool, GetConfig)(_Out_ PWSTR buffer, _Out_ int *buffer_size) = 0;
IFACEMETHOD_(void, SetConfig)(PCWSTR serializedPowerToysSettingsJson) = 0;
IFACEMETHOD_(void, CallCustomAction)(PCWSTR action) = 0;
IFACEMETHOD_(Settings, GetSettings)() = 0;
IFACEMETHOD_(const Settings*, GetSettings)() const = 0;
};
winrt::com_ptr<IFancyZonesSettings> MakeFancyZonesSettings(HINSTANCE hinstance, PCWSTR config) noexcept;

View File

@@ -66,9 +66,9 @@ void Zone::SizeWindowToZone(HWND window, HWND zoneWindow) noexcept
{
return;
}
// Take care of 1px border
RECT zoneRect = m_zoneRect;
RECT newWindowRect = m_zoneRect;
RECT windowRect{};
::GetWindowRect(window, &windowRect);
@@ -82,38 +82,54 @@ void Zone::SizeWindowToZone(HWND window, HWND zoneWindow) noexcept
const auto left_margin = frameRect.left - windowRect.left;
const auto right_margin = frameRect.right - windowRect.right;
const auto bottom_margin = frameRect.bottom - windowRect.bottom;
zoneRect.left -= left_margin;
zoneRect.right -= right_margin;
zoneRect.bottom -= bottom_margin;
newWindowRect.left -= left_margin;
newWindowRect.right -= right_margin;
newWindowRect.bottom -= bottom_margin;
}
// Map to screen coords
MapWindowRect(zoneWindow, nullptr, &zoneRect);
MapWindowRect(zoneWindow, nullptr, &newWindowRect);
MONITORINFO mi{sizeof(mi)};
MONITORINFO mi{ sizeof(mi) };
if (GetMonitorInfoW(MonitorFromWindow(zoneWindow, MONITOR_DEFAULTTONEAREST), &mi))
{
const auto taskbar_left_size = std::abs(mi.rcMonitor.left - mi.rcWork.left);
const auto taskbar_top_size = std::abs(mi.rcMonitor.top - mi.rcWork.top);
OffsetRect(&zoneRect, -taskbar_left_size, -taskbar_top_size);
OffsetRect(&newWindowRect, -taskbar_left_size, -taskbar_top_size);
if (accountForUnawareness)
{
zoneRect.left = max(mi.rcMonitor.left, zoneRect.left);
zoneRect.right = min(mi.rcMonitor.right - taskbar_left_size, zoneRect.right);
zoneRect.top = max(mi.rcMonitor.top, zoneRect.top);
zoneRect.bottom = min(mi.rcMonitor.bottom - taskbar_top_size, zoneRect.bottom);
newWindowRect.left = max(mi.rcMonitor.left, newWindowRect.left);
newWindowRect.right = min(mi.rcMonitor.right - taskbar_left_size, newWindowRect.right);
newWindowRect.top = max(mi.rcMonitor.top, newWindowRect.top);
newWindowRect.bottom = min(mi.rcMonitor.bottom - taskbar_top_size, newWindowRect.bottom);
}
}
if ((::GetWindowLong(window, GWL_STYLE) & WS_SIZEBOX) == 0)
{
newWindowRect.right = newWindowRect.left + (windowRect.right - windowRect.left);
newWindowRect.bottom = newWindowRect.top + (windowRect.bottom - windowRect.top);
}
WINDOWPLACEMENT placement{};
::GetWindowPlacement(window, &placement);
placement.rcNormalPosition = zoneRect;
placement.flags |= WPF_ASYNCWINDOWPLACEMENT;
//wait if SW_SHOWMINIMIZED would be removed from window (Issue #1685)
for (int i = 0; i < 5 && (placement.showCmd & SW_SHOWMINIMIZED) != 0; i++)
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
::GetWindowPlacement(window, &placement);
}
// Do not restore minimized windows. We change their placement though so they restore to the correct zone.
if ((placement.showCmd & SW_SHOWMINIMIZED) == 0)
{
placement.showCmd = SW_RESTORE | SW_SHOWNA;
}
placement.rcNormalPosition = newWindowRect;
placement.flags |= WPF_ASYNCWINDOWPLACEMENT;
::SetWindowPlacement(window, &placement);
// Do it again, allowing Windows to resize the window and set correct scaling
// This fixes Issue #365

View File

@@ -130,8 +130,8 @@ public:
GetZones() noexcept { return m_zones; }
IFACEMETHODIMP_(void)
MoveWindowIntoZoneByIndex(HWND window, HWND zoneWindow, int index) noexcept;
IFACEMETHODIMP_(void)
MoveWindowIntoZoneByDirection(HWND window, HWND zoneWindow, DWORD vkCode) noexcept;
IFACEMETHODIMP_(bool)
MoveWindowIntoZoneByDirection(HWND window, HWND zoneWindow, DWORD vkCode, bool cycle) noexcept;
IFACEMETHODIMP_(void)
MoveWindowIntoZoneByPoint(HWND window, HWND zoneWindow, POINT ptClient) noexcept;
IFACEMETHODIMP_(bool)
@@ -226,7 +226,7 @@ ZoneSet::MoveWindowIntoZoneByIndex(HWND window, HWND windowZone, int index) noex
if (index >= int(m_zones.size()))
{
index = 0;
return;
}
while (auto zoneDrop = ZoneFromWindow(window))
@@ -240,12 +240,12 @@ ZoneSet::MoveWindowIntoZoneByIndex(HWND window, HWND windowZone, int index) noex
}
}
IFACEMETHODIMP_(void)
ZoneSet::MoveWindowIntoZoneByDirection(HWND window, HWND windowZone, DWORD vkCode) noexcept
IFACEMETHODIMP_(bool)
ZoneSet::MoveWindowIntoZoneByDirection(HWND window, HWND windowZone, DWORD vkCode, bool cycle) noexcept
{
if (m_zones.empty())
{
return;
return false;
}
winrt::com_ptr<IZone> oldZone = nullptr;
@@ -262,6 +262,11 @@ ZoneSet::MoveWindowIntoZoneByDirection(HWND window, HWND windowZone, DWORD vkCod
{
if (iter == m_zones.begin())
{
if (!cycle)
{
oldZone->RemoveWindowFromZone(window, false);
return false;
}
iter = m_zones.end();
}
iter--;
@@ -271,6 +276,11 @@ ZoneSet::MoveWindowIntoZoneByDirection(HWND window, HWND windowZone, DWORD vkCod
iter++;
if (iter == m_zones.end())
{
if (!cycle)
{
oldZone->RemoveWindowFromZone(window, false);
return false;
}
iter = m_zones.begin();
}
}
@@ -283,7 +293,9 @@ ZoneSet::MoveWindowIntoZoneByDirection(HWND window, HWND windowZone, DWORD vkCod
oldZone->RemoveWindowFromZone(window, false);
}
newZone->AddWindowToZone(window, windowZone, true);
return true;
}
return false;
}
IFACEMETHODIMP_(void)
@@ -560,7 +572,7 @@ bool ZoneSet::CalculateGridZones(Rect workArea, JSONHelpers::GridLayoutInfo grid
// Note: The expressions below are carefully written to
// make the sum of all zones' sizes exactly total{Width|Height}
long long totalPercents = 0;
int totalPercents = 0;
for (int row = 0; row < gridLayoutInfo.rows(); row++)
{
rowInfo[row].Start = totalPercents * totalHeight / C_MULTIPLIER + (row + 1) * spacing;

View File

@@ -57,8 +57,12 @@ interface __declspec(uuid("{E4839EB7-669D-49CF-84A9-71A2DFD851A3}")) IZoneSet :
* @param zoneWindow The m_window of a ZoneWindow, it's a hidden window representing the
* current monitor desktop work area.
* @param vkCode Pressed arrow key.
* @param cycle Whether we should move window to the first zone if we reached last zone in layout.
*
* @returns Boolean which is always true if cycle argument is set, otherwise indicating if there is more
* zones left in the zone layout in which window can move.
*/
IFACEMETHOD_(void, MoveWindowIntoZoneByDirection)(HWND window, HWND zoneWindow, DWORD vkCode) = 0;
IFACEMETHOD_(bool, MoveWindowIntoZoneByDirection)(HWND window, HWND zoneWindow, DWORD vkCode, bool cycle) = 0;
/**
* Assign window to the zone based on cursor coordinates.
*
@@ -75,7 +79,8 @@ interface __declspec(uuid("{E4839EB7-669D-49CF-84A9-71A2DFD851A3}")) IZoneSet :
* @param monitorInfo Information about monitor on which zone layout is applied.
* @param zoneCount Number of zones inside zone layout.
* @param spacing Spacing between zones in pixels.
* @returns Boolean if calculation was successful.
*
* @returns Boolean indicating if calculation was successful.
*/
IFACEMETHOD_(bool, CalculateZones)(MONITORINFO monitorInfo, int zoneCount, int spacing) = 0;
};

View File

@@ -10,6 +10,8 @@
#include <ShellScalingApi.h>
#include <mutex>
#include <gdiplus.h>
namespace ZoneWindowUtils
{
const std::wstring& GetActiveZoneSetTmpPath()
@@ -92,146 +94,70 @@ namespace ZoneWindowDrawUtils
int thickness{};
};
bool IsOccluded(const std::vector<winrt::com_ptr<IZone>>& zones, POINT pt, size_t index) noexcept
{
size_t i = 1;
for (auto iter = zones.begin(); iter != zones.end(); iter++)
{
if (winrt::com_ptr<IZone> zone = iter->try_as<IZone>())
{
if (i < index)
{
if (PtInRect(&zone->GetZoneRect(), pt))
{
return true;
}
}
}
i++;
}
return false;
}
void DrawBackdrop(wil::unique_hdc& hdc, RECT const& clientRect) noexcept
{
FillRectARGB(hdc, &clientRect, 0, RGB(0, 0, 0), false);
}
void DrawIndex(wil::unique_hdc& hdc, POINT offset, size_t index, int padding, int size, bool flipX, bool flipY, COLORREF colorFill)
void DrawIndex(wil::unique_hdc& hdc, Rect rect, size_t index)
{
RECT rect = { offset.x, offset.y, offset.x + size, offset.y + size };
for (int y = 0; y < 3; y++)
{
for (int x = 0; x < 3; x++)
{
RECT useRect = rect;
if (flipX)
{
if (x == 0)
useRect.left += (size + padding + size + padding);
else if (x == 2)
useRect.left -= (size + padding + size + padding);
useRect.right = useRect.left + size;
}
Gdiplus::Graphics g(hdc.get());
if (flipY)
{
if (y == 0)
useRect.top += (size + padding + size + padding);
else if (y == 2)
useRect.top -= (size + padding + size + padding);
useRect.bottom = useRect.top + size;
}
Gdiplus::FontFamily fontFamily(L"Segoe ui");
Gdiplus::Font font(&fontFamily, 80, Gdiplus::FontStyleRegular, Gdiplus::UnitPixel);
Gdiplus::SolidBrush solidBrush(Gdiplus::Color(255, 0, 0, 0));
FillRectARGB(hdc, &useRect, 200, RGB(50, 50, 50), true);
std::wstring text = std::to_wstring(index);
RECT inside = useRect;
InflateRect(&inside, -2, -2);
g.SetTextRenderingHint(Gdiplus::TextRenderingHintAntiAlias);
Gdiplus::StringFormat stringFormat = new Gdiplus::StringFormat();
stringFormat.SetAlignment(Gdiplus::StringAlignmentCenter);
stringFormat.SetLineAlignment(Gdiplus::StringAlignmentCenter);
FillRectARGB(hdc, &inside, 100, colorFill, true);
Gdiplus::RectF gdiRect(static_cast<Gdiplus::REAL>(rect.left()),
static_cast<Gdiplus::REAL>(rect.top()),
static_cast<Gdiplus::REAL>(rect.width()),
static_cast<Gdiplus::REAL>(rect.height()));
rect.left += (size + padding);
rect.right = rect.left + size;
if (--index == 0)
{
return;
}
}
rect.left = offset.x;
rect.right = rect.left + size;
rect.top += (size + padding);
rect.bottom = rect.top + size;
}
g.DrawString(text.c_str(), -1, &font, gdiRect, &stringFormat, &solidBrush);
}
void DrawZone(wil::unique_hdc& hdc, ColorSetting const& colorSetting, winrt::com_ptr<IZone> zone, const std::vector<winrt::com_ptr<IZone>>& zones, bool flashMode) noexcept
{
RECT zoneRect = zone->GetZoneRect();
if (colorSetting.borderAlpha > 0)
{
FillRectARGB(hdc, &zoneRect, colorSetting.borderAlpha, colorSetting.border, false);
InflateRect(&zoneRect, colorSetting.thickness, colorSetting.thickness);
}
FillRectARGB(hdc, &zoneRect, colorSetting.fillAlpha, colorSetting.fill, false);
if (flashMode)
{
return;
}
COLORREF const colorFill = RGB(255, 255, 255);
Gdiplus::Graphics g(hdc.get());
Gdiplus::Color fillColor(colorSetting.fillAlpha, GetRValue(colorSetting.fill), GetGValue(colorSetting.fill), GetBValue(colorSetting.fill));
Gdiplus::Color borderColor(colorSetting.borderAlpha, GetRValue(colorSetting.border), GetGValue(colorSetting.border), GetBValue(colorSetting.border));
size_t const index = zone->Id();
int const padding = 5;
int const size = 10;
POINT offset = { zoneRect.left + padding, zoneRect.top + padding };
if (!IsOccluded(zones, offset, index))
{
DrawIndex(hdc, offset, index, padding, size, false, false, colorFill); // top left
return;
}
Gdiplus::Rect rectangle(zoneRect.left, zoneRect.top, zoneRect.right - zoneRect.left - 1, zoneRect.bottom - zoneRect.top - 1);
offset.x = zoneRect.right - ((padding + size) * 3);
if (!IsOccluded(zones, offset, index))
{
DrawIndex(hdc, offset, index, padding, size, true, false, colorFill); // top right
return;
}
Gdiplus::Pen pen(borderColor, static_cast<Gdiplus::REAL>(colorSetting.thickness));
g.FillRectangle(new Gdiplus::SolidBrush(fillColor), rectangle);
g.DrawRectangle(&pen, rectangle);
offset.y = zoneRect.bottom - ((padding + size) * 3);
if (!IsOccluded(zones, offset, index))
if (!flashMode)
{
DrawIndex(hdc, offset, index, padding, size, true, true, colorFill); // bottom right
return;
DrawIndex(hdc, zoneRect, zone->Id());
}
offset.x = zoneRect.left + padding;
DrawIndex(hdc, offset, index, padding, size, false, true, colorFill); // bottom left
}
void DrawActiveZoneSet(wil::unique_hdc& hdc, COLORREF highlightColor, int highlightOpacity, const std::vector<winrt::com_ptr<IZone>>& zones, const winrt::com_ptr<IZone>& highlightZone, bool flashMode, bool drawHints) noexcept
void DrawActiveZoneSet(wil::unique_hdc& hdc,
COLORREF zoneColor,
COLORREF zoneBorderColor,
COLORREF highlightColor,
int zoneOpacity,
const std::vector<winrt::com_ptr<IZone>>& zones,
const winrt::com_ptr<IZone>& highlightZone,
bool flashMode,
bool drawHints) noexcept
{
static constexpr std::array<COLORREF, 9> colors{
RGB(75, 75, 85),
RGB(150, 150, 160),
RGB(100, 100, 110),
RGB(125, 125, 135),
RGB(225, 225, 235),
RGB(25, 25, 35),
RGB(200, 200, 210),
RGB(50, 50, 60),
RGB(175, 175, 185),
};
// { fillAlpha, fill, borderAlpha, border, thickness }
ColorSetting const colorHints{ OpacitySettingToAlpha(zoneOpacity), RGB(81, 92, 107), 255, RGB(104, 118, 138), -2 };
ColorSetting colorViewer{ OpacitySettingToAlpha(zoneOpacity), 0, 255, RGB(40, 50, 60), -2 };
ColorSetting colorHighlight{ OpacitySettingToAlpha(zoneOpacity), 0, 255, 0, -2 };
ColorSetting const colorFlash{ OpacitySettingToAlpha(zoneOpacity), RGB(81, 92, 107), 200, RGB(104, 118, 138), -2 };
// ColorSetting { fillAlpha, fill, borderAlpha, border, thickness }
ColorSetting const colorHints{ 225, RGB(81, 92, 107), 255, RGB(104, 118, 138), -2 };
ColorSetting colorViewer{ OpacitySettingToAlpha(highlightOpacity), 0, 255, RGB(40, 50, 60), -2 };
ColorSetting colorHighlight{ OpacitySettingToAlpha(highlightOpacity), 0, 255, 0, -2 };
ColorSetting const colorFlash{ 200, RGB(81, 92, 107), 200, RGB(104, 118, 138), -2 };
const size_t maxColorIndex = min(size(zones) - 1, size(colors) - 1);
size_t colorIndex = maxColorIndex;
for (auto iter = zones.begin(); iter != zones.end(); iter++)
{
winrt::com_ptr<IZone> zone = iter->try_as<IZone>();
@@ -251,20 +177,17 @@ namespace ZoneWindowDrawUtils
DrawZone(hdc, colorHints, zone, zones, flashMode);
}
{
colorViewer.fill = colors[colorIndex];
colorViewer.fill = zoneColor;
colorViewer.border = zoneBorderColor;
DrawZone(hdc, colorViewer, zone, zones, flashMode);
}
}
colorIndex = colorIndex != 0 ? colorIndex - 1 : maxColorIndex;
}
if (highlightZone)
{
colorHighlight.fill = highlightColor;
colorHighlight.border = RGB(
max(0, GetRValue(colorHighlight.fill) - 25),
max(0, GetGValue(colorHighlight.fill) - 25),
max(0, GetBValue(colorHighlight.fill) - 25));
colorHighlight.border = zoneBorderColor;
DrawZone(hdc, colorHighlight, highlightZone, zones, flashMode);
}
}
@@ -274,18 +197,21 @@ struct ZoneWindow : public winrt::implements<ZoneWindow, IZoneWindow>
{
public:
ZoneWindow(HINSTANCE hinstance);
~ZoneWindow();
bool Init(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, const std::wstring& uniqueId, bool flashZones);
IFACEMETHODIMP MoveSizeEnter(HWND window, bool dragEnabled) noexcept;
IFACEMETHODIMP MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled) noexcept;
IFACEMETHODIMP MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept;
IFACEMETHODIMP MoveSizeCancel() noexcept;
IFACEMETHODIMP_(void)
RestoreOrginalTransparency() noexcept;
IFACEMETHODIMP_(bool)
IsDragEnabled() noexcept { return m_dragEnabled; }
IFACEMETHODIMP_(void)
MoveWindowIntoZoneByIndex(HWND window, int index) noexcept;
IFACEMETHODIMP_(void)
MoveWindowIntoZoneByDirection(HWND window, DWORD vkCode) noexcept;
IFACEMETHODIMP_(bool)
MoveWindowIntoZoneByDirection(HWND window, DWORD vkCode, bool cycle) noexcept;
IFACEMETHODIMP_(void)
CycleActiveZoneSet(DWORD vkCode) noexcept;
IFACEMETHODIMP_(std::wstring)
@@ -296,13 +222,15 @@ public:
SaveWindowProcessToZoneIndex(HWND window) noexcept;
IFACEMETHODIMP_(IZoneSet*)
ActiveZoneSet() noexcept { return m_activeZoneSet.get(); }
IFACEMETHODIMP_(void)
ShowZoneWindow() noexcept;
IFACEMETHODIMP_(void)
HideZoneWindow() noexcept;
protected:
static LRESULT CALLBACK s_WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept;
private:
void ShowZoneWindow() noexcept;
void HideZoneWindow() noexcept;
void LoadSettings() noexcept;
void InitializeZoneSets(MONITORINFO const& mi) noexcept;
void CalculateZoneSet() noexcept;
@@ -330,6 +258,14 @@ private:
size_t m_keyCycle{};
static const UINT m_showAnimationDuration = 200; // ms
static const UINT m_flashDuration = 700; // ms
HWND draggedWindow = nullptr;
long draggedWindowExstyle = 0;
COLORREF draggedWindowCrKey = RGB(0, 0, 0);
DWORD draggedWindowDwFlags = 0;
BYTE draggedWindowInitialAlpha = 0;
ULONG_PTR gdiplusToken;
};
ZoneWindow::ZoneWindow(HINSTANCE hinstance)
@@ -341,6 +277,16 @@ ZoneWindow::ZoneWindow(HINSTANCE hinstance)
wcex.lpszClassName = L"SuperFancyZones_ZoneWindow";
wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW);
RegisterClassExW(&wcex);
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
}
ZoneWindow::~ZoneWindow()
{
RestoreOrginalTransparency();
Gdiplus::GdiplusShutdown(gdiplusToken);
}
bool ZoneWindow::Init(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, const std::wstring& uniqueId, bool flashZones)
@@ -398,6 +344,22 @@ IFACEMETHODIMP ZoneWindow::MoveSizeEnter(HWND window, bool dragEnabled) noexcept
return E_INVALIDARG;
}
if (m_host->isMakeDraggedWindowTransparentActive())
{
RestoreOrginalTransparency();
draggedWindowExstyle = GetWindowLong(window, GWL_EXSTYLE);
draggedWindow = window;
SetWindowLong(window,
GWL_EXSTYLE,
draggedWindowExstyle | WS_EX_LAYERED);
GetLayeredWindowAttributes(window, &draggedWindowCrKey, &draggedWindowInitialAlpha, &draggedWindowDwFlags);
SetLayeredWindowAttributes(window, 0, (255 * 50) / 100, LWA_ALPHA);
}
m_dragEnabled = dragEnabled;
m_windowMoveSize = window;
m_drawHints = true;
@@ -435,6 +397,8 @@ IFACEMETHODIMP ZoneWindow::MoveSizeUpdate(POINT const& ptScreen, bool dragEnable
IFACEMETHODIMP ZoneWindow::MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept
{
RestoreOrginalTransparency();
if (m_windowMoveSize != window)
{
return E_INVALIDARG;
@@ -455,10 +419,15 @@ IFACEMETHODIMP ZoneWindow::MoveSizeEnd(HWND window, POINT const& ptScreen) noexc
return S_OK;
}
IFACEMETHODIMP ZoneWindow::MoveSizeCancel() noexcept
IFACEMETHODIMP_(void)
ZoneWindow::RestoreOrginalTransparency() noexcept
{
HideZoneWindow();
return S_OK;
if (m_host->isMakeDraggedWindowTransparentActive() && draggedWindow != nullptr)
{
SetLayeredWindowAttributes(draggedWindow, draggedWindowCrKey, draggedWindowInitialAlpha, draggedWindowDwFlags);
SetWindowLong(draggedWindow, GWL_EXSTYLE, draggedWindowExstyle);
draggedWindow = nullptr;
}
}
IFACEMETHODIMP_(void)
@@ -470,14 +439,18 @@ ZoneWindow::MoveWindowIntoZoneByIndex(HWND window, int index) noexcept
}
}
IFACEMETHODIMP_(void)
ZoneWindow::MoveWindowIntoZoneByDirection(HWND window, DWORD vkCode) noexcept
IFACEMETHODIMP_(bool)
ZoneWindow::MoveWindowIntoZoneByDirection(HWND window, DWORD vkCode, bool cycle) noexcept
{
if (m_activeZoneSet)
{
m_activeZoneSet->MoveWindowIntoZoneByDirection(window, m_window.get(), vkCode);
SaveWindowProcessToZoneIndex(window);
if (m_activeZoneSet->MoveWindowIntoZoneByDirection(window, m_window.get(), vkCode, cycle))
{
SaveWindowProcessToZoneIndex(window);
return true;
}
}
return false;
}
IFACEMETHODIMP_(void)
@@ -514,8 +487,8 @@ ZoneWindow::SaveWindowProcessToZoneIndex(HWND window) noexcept
}
}
#pragma region private
void ZoneWindow::ShowZoneWindow() noexcept
IFACEMETHODIMP_(void)
ZoneWindow::ShowZoneWindow() noexcept
{
if (m_window)
{
@@ -536,7 +509,8 @@ void ZoneWindow::ShowZoneWindow() noexcept
}
}
void ZoneWindow::HideZoneWindow() noexcept
IFACEMETHODIMP_(void)
ZoneWindow::HideZoneWindow() noexcept
{
if (m_window)
{
@@ -548,6 +522,8 @@ void ZoneWindow::HideZoneWindow() noexcept
}
}
#pragma region private
void ZoneWindow::LoadSettings() noexcept
{
JSONHelpers::FancyZonesDataInstance().AddDevice(m_uniqueId);
@@ -630,7 +606,8 @@ LRESULT ZoneWindow::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept
{
switch (message)
{
case WM_NCDESTROY: {
case WM_NCDESTROY:
{
::DefWindowProc(m_window.get(), message, wparam, lparam);
SetWindowLongPtr(m_window.get(), GWLP_USERDATA, 0);
}
@@ -640,7 +617,8 @@ LRESULT ZoneWindow::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept
return 1;
case WM_PRINTCLIENT:
case WM_PAINT: {
case WM_PAINT:
{
PAINTSTRUCT ps;
wil::unique_hdc hdc{ reinterpret_cast<HDC>(wparam) };
if (!hdc)
@@ -677,9 +655,18 @@ void ZoneWindow::OnPaint(wil::unique_hdc& hdc) noexcept
if (bufferedPaint)
{
ZoneWindowDrawUtils::DrawBackdrop(hdcMem, clientRect);
if (m_activeZoneSet && m_host)
{
ZoneWindowDrawUtils::DrawActiveZoneSet(hdcMem, m_host->GetZoneHighlightColor(), m_host->GetZoneHighlightOpacity(), m_activeZoneSet->GetZones(), m_highlightZone, m_flashMode, m_drawHints);
ZoneWindowDrawUtils::DrawActiveZoneSet(hdcMem,
m_host->GetZoneColor(),
m_host->GetZoneBorderColor(),
m_host->GetZoneHighlightColor(),
m_host->GetZoneHighlightOpacity(),
m_activeZoneSet->GetZones(),
m_highlightZone,
m_flashMode,
m_drawHints);
}
EndBufferedPaint(bufferedPaint, TRUE);
@@ -757,6 +744,12 @@ void ZoneWindow::CycleActiveZoneSetInternal(DWORD wparam, Trace::ZoneWindow::Inp
void ZoneWindow::FlashZones() noexcept
{
// "Turning FLASHING_ZONE option off"
if (true)
{
return;
}
m_flashMode = true;
ShowWindow(m_window.get(), SW_SHOWNA);

View File

@@ -41,10 +41,6 @@ interface __declspec(uuid("{7F017528-8110-4FB3-BE41-F472969C2560}")) IZoneWindow
* @param ptScreen Cursor coordinates where window is droped.
*/
IFACEMETHOD(MoveSizeEnd)(HWND window, POINT const& ptScreen) = 0;
/**
* Abort tracking down of window position and giving zone layout hints (if dragging functionality is enabled).
*/
IFACEMETHOD(MoveSizeCancel)() = 0;
/**
* @returns Boolean indicating is giving hints about active zone layout enabled. Hints are
* given while dragging window while holding SHIFT key.
@@ -62,14 +58,22 @@ interface __declspec(uuid("{7F017528-8110-4FB3-BE41-F472969C2560}")) IZoneWindow
*
* @param window Handle of window which should be assigned to zone.
* @param vkCode Pressed arrow key.
* @param cycle Whether we should move window to the first zone if we reached last zone in layout.
*
* @returns Boolean which is always true if cycle argument is set, otherwise indicating if there is more
* zones left in the zone layout in which window can move.
*/
IFACEMETHOD_(void, MoveWindowIntoZoneByDirection)(HWND window, DWORD vkCode) = 0;
IFACEMETHOD_(bool, MoveWindowIntoZoneByDirection)(HWND window, DWORD vkCode, bool cycle) = 0;
/**
* Cycle through active zone layouts (giving hints about each layout).
*
* @param vkCode Pressed key representing layout index.
*/
IFACEMETHOD_(void, CycleActiveZoneSet)(DWORD vkCode) = 0;
/**
* Restore orginal transaprency of dragged window.
*/
IFACEMETHOD_(void, RestoreOrginalTransparency) () = 0;
/**
* Save information about zone in which window was assigned, when closing the window.
* Used once we open same window again to assign it to its previous zone.
@@ -89,6 +93,8 @@ interface __declspec(uuid("{7F017528-8110-4FB3-BE41-F472969C2560}")) IZoneWindow
* @returns Active zone layout for this work area.
*/
IFACEMETHOD_(IZoneSet*, ActiveZoneSet)() = 0;
IFACEMETHOD_(void, ShowZoneWindow)() = 0;
IFACEMETHOD_(void, HideZoneWindow)() = 0;
};
winrt::com_ptr<IZoneWindow> MakeZoneWindow(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor,

View File

@@ -1,17 +1,24 @@
#define IDS_SETTING_DESCRIPTION_SHIFTDRAG 101
#define IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS 102
#define IDS_SETTING_DESCRIPTION_DISPLAYCHANGE_MOVEWINDOWS 103
#define IDS_SETTING_DESCRIPTION_ZONESETCHANGE_MOVEWINDOWS 104
#define IDS_SETTING_DESCRIPTION_ZONESETCHANGE_FLASHZONES 105
#define IDS_SETTING_DESCRIPTION_VIRTUALDESKTOPCHANGE_MOVEWINDOWS 106
#define IDS_SETTING_DESCRIPTION_ZONEHIGHLIGHTCOLOR 107
#define IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS 108
#define IDS_SETTING_DESCRIPTION_USE_CURSORPOS_EDITOR_STARTUPSCREEN 109
#define IDS_SETTING_DESCRIPTION 110
#define IDS_SETTING_LAUNCH_EDITOR_LABEL 111
#define IDS_SETTING_LAUNCH_EDITOR_BUTTON 112
#define IDS_SETTING_LAUNCH_EDITOR_DESCRIPTION 113
#define IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL 114
#define IDS_SETTING_EXCLCUDED_APPS_DESCRIPTION 115
#define IDS_SETTINGS_HIGHLIGHT_OPACITY 116
#define IDS_FANCYZONES 117
#define IDS_SETTING_DESCRIPTION_SHIFTDRAG 101
#define IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS 102
#define IDS_SETTING_DESCRIPTION_DISPLAYCHANGE_MOVEWINDOWS 103
#define IDS_SETTING_DESCRIPTION_ZONESETCHANGE_MOVEWINDOWS 104
#define IDS_SETTING_DESCRIPTION_ZONESETCHANGE_FLASHZONES 105
#define IDS_SETTING_DESCRIPTION_VIRTUALDESKTOPCHANGE_MOVEWINDOWS 106
#define IDS_SETTING_DESCRIPTION_SHOW_FANCY_ZONES_ON_ALL_MONITORS 107
#define IDS_SETTING_DESCRIPTION_MAKE_DRAGGED_WINDOW_TRANSPARENT 108
#define IDS_SETTING_DESCRIPTION_ZONECOLOR 109
#define IDS_SETTING_DESCRIPTION_ZONE_BORDER_COLOR 110
#define IDS_SETTING_DESCRIPTION_ZONEHIGHLIGHTCOLOR 111
#define IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS 112
#define IDS_SETTING_DESCRIPTION_USE_CURSORPOS_EDITOR_STARTUPSCREEN 113
#define IDS_SETTING_DESCRIPTION 114
#define IDS_SETTING_LAUNCH_EDITOR_LABEL 115
#define IDS_SETTING_LAUNCH_EDITOR_BUTTON 116
#define IDS_SETTING_LAUNCH_EDITOR_DESCRIPTION 117
#define IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL 118
#define IDS_SETTING_EXCLCUDED_APPS_DESCRIPTION 119
#define IDS_SETTINGS_HIGHLIGHT_OPACITY 120
#define IDS_FANCYZONES 121
#define IDS_CANT_DRAG_ELEVATED 122
#define IDS_CANT_DRAG_ELEVATED_LEARN_MORE 123
#define IDS_CANT_DRAG_ELEVATED_DIALOG_DONT_SHOW_AGAIN 124

View File

@@ -147,6 +147,16 @@ void Trace::FancyZones::DataChanged() noexcept
TraceLoggingWideString(activeZoneSetInfo.c_str(), "ActiveZoneSetsList"));
}
void Trace::FancyZones::EditorLaunched(int value) noexcept
{
TraceLoggingWrite(
g_hProvider,
"FancyZones_EditorLaunch",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingInt32(value, "Value"));
}
void Trace::SettingsChanged(const Settings& settings) noexcept
{
const auto& editorHotkey = settings.editorHotkey;
@@ -170,6 +180,10 @@ void Trace::SettingsChanged(const Settings& settings) noexcept
TraceLoggingBoolean(settings.overrideSnapHotkeys, "OverrideSnapHotKeys"),
TraceLoggingBoolean(settings.appLastZone_moveWindows, "MoveWindowsToLastZoneOnAppOpening"),
TraceLoggingBoolean(settings.use_cursorpos_editor_startupscreen, "UseCursorPosOnEditorStartup"),
TraceLoggingBoolean(settings.showZonesOnAllMonitors, "ShowZonesOnAllMonitors"),
TraceLoggingBoolean(settings.makeDraggedWindowTransparent, "MakeDraggedWindowTransparent"),
TraceLoggingWideString(settings.zoneColor.c_str(), "ZoneColor"),
TraceLoggingWideString(settings.zoneBorderColor.c_str(), "ZoneBorderColor"),
TraceLoggingWideString(settings.zoneHightlightColor.c_str(), "ZoneHighlightColor"),
TraceLoggingInt32(settings.zoneHighlightOpacity, "ZoneHighlightOpacity"),
TraceLoggingWideString(hotkeyStr.c_str(), "Hotkey"),

View File

@@ -15,6 +15,7 @@ public:
static void EnableFancyZones(bool enabled) noexcept;
static void OnKeyDown(DWORD vkCode, bool win, bool control, bool inMoveSize) noexcept;
static void DataChanged() noexcept;
static void EditorLaunched(int value) noexcept;
};
static void SettingsChanged(const Settings& settings) noexcept;

View File

@@ -25,3 +25,86 @@ UINT GetDpiForMonitor(HMONITOR monitor) noexcept
return (dpi == 0) ? DPIAware::DEFAULT_DPI : dpi;
}
void OrderMonitors(std::vector<std::pair<HMONITOR, RECT>>& monitorInfo)
{
const size_t nMonitors = monitorInfo.size();
// blocking[i][j] - whether monitor i blocks monitor j in the ordering, i.e. monitor i should go before monitor j
std::vector<std::vector<bool>> blocking(nMonitors, std::vector<bool>(nMonitors, false));
// blockingCount[j] - the number of monitors which block monitor j
std::vector<size_t> blockingCount(nMonitors, 0);
for (size_t i = 0; i < nMonitors; i++)
{
RECT rectI = monitorInfo[i].second;
for (size_t j = 0; j < nMonitors; j++)
{
RECT rectJ = monitorInfo[j].second;
blocking[i][j] = rectI.top < rectJ.bottom && rectI.left < rectJ.right && i != j;
if (blocking[i][j])
{
blockingCount[j]++;
}
}
}
// used[i] - whether the sorting algorithm has used monitor i so far
std::vector<bool> used(nMonitors, false);
// the sorted sequence of monitors
std::vector<std::pair<HMONITOR, RECT>> sortedMonitorInfo;
for (size_t iteration = 0; iteration < nMonitors; iteration++)
{
// Indices of candidates to become the next monitor in the sequence
std::vector<size_t> candidates;
// First, find indices of all unblocked monitors
for (size_t i = 0; i < nMonitors; i++)
{
if (blockingCount[i] == 0 && !used[i])
{
candidates.push_back(i);
}
}
// In the unlikely event that there are no unblocked monitors, declare all unused monitors as candidates.
if (candidates.empty())
{
for (size_t i = 0; i < nMonitors; i++)
{
if (!used[i])
{
candidates.push_back(i);
}
}
}
// Pick the lexicographically smallest monitor as the next one
size_t smallest = candidates[0];
for (size_t j = 1; j < candidates.size(); j++)
{
size_t current = candidates[j];
// Compare (top, left) lexicographically
if (std::tie(monitorInfo[current].second.top, monitorInfo[current].second.left)
< std::tie(monitorInfo[smallest].second.top, monitorInfo[smallest].second.left))
{
smallest = current;
}
}
used[smallest] = true;
sortedMonitorInfo.push_back(monitorInfo[smallest]);
for (size_t i = 0; i < nMonitors; i++)
{
if (blocking[smallest][i])
{
blockingCount[i]--;
}
}
}
monitorInfo = std::move(sortedMonitorInfo);
}

View File

@@ -1,14 +1,18 @@
#pragma once
#include "gdiplus.h"
struct Rect
{
Rect() {}
Rect(RECT rect) : m_rect(rect)
Rect(RECT rect) :
m_rect(rect)
{
}
Rect(RECT rect, UINT dpi) : m_rect(rect)
Rect(RECT rect, UINT dpi) :
m_rect(rect)
{
m_rect.right = m_rect.left + MulDiv(m_rect.right - m_rect.left, dpi, 96);
m_rect.bottom = m_rect.top + MulDiv(m_rect.bottom - m_rect.top, dpi, 96);
@@ -38,7 +42,7 @@ inline void MakeWindowTransparent(HWND window)
}
}
inline void InitRGB(_Out_ RGBQUAD *quad, BYTE alpha, COLORREF color)
inline void InitRGB(_Out_ RGBQUAD* quad, BYTE alpha, COLORREF color)
{
ZeroMemory(quad, sizeof(*quad));
quad->rgbReserved = alpha;
@@ -47,7 +51,7 @@ inline void InitRGB(_Out_ RGBQUAD *quad, BYTE alpha, COLORREF color)
quad->rgbBlue = GetBValue(color) * alpha / 255;
}
inline void FillRectARGB(wil::unique_hdc& hdc, RECT const *prcFill, BYTE alpha, COLORREF color, bool blendAlpha)
inline void FillRectARGB(wil::unique_hdc& hdc, RECT const* prcFill, BYTE alpha, COLORREF color, bool blendAlpha)
{
BITMAPINFO bi;
ZeroMemory(&bi, sizeof(bi));
@@ -60,63 +64,30 @@ inline void FillRectARGB(wil::unique_hdc& hdc, RECT const *prcFill, BYTE alpha,
RECT fillRect;
CopyRect(&fillRect, prcFill);
if ((alpha == 255) || !blendAlpha)
{
// Opaque or the caller does not want to blend the alpha
RGBQUAD bitmapBits;
InitRGB(&bitmapBits, alpha, color);
StretchDIBits(
hdc.get(),
fillRect.left,
fillRect.top,
fillRect.right - fillRect.left,
fillRect.bottom - fillRect.top,
0, 0, 1, 1, &bitmapBits, &bi, DIB_RGB_COLORS, SRCCOPY);
}
else
{
if (wil::unique_hdc hdcSrc{ CreateCompatibleDC(hdc.get()) })
{
void* pBitmapBits;
if (wil::unique_hbitmap bitmapSource{ CreateDIBSection(hdcSrc.get(), &bi, DIB_RGB_COLORS, &pBitmapBits, nullptr, 0) })
{
InitRGB(reinterpret_cast<RGBQUAD *>(pBitmapBits), alpha, color);
wil::unique_select_object bitmapOld{ SelectObject(hdcSrc.get(), bitmapSource.get()) };
BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
GdiAlphaBlend(
hdc.get(),
fillRect.left,
fillRect.top,
fillRect.right - fillRect.left,
fillRect.bottom - fillRect.top,
hdcSrc.get(), 0, 0, 1, 1, bf);
}
}
}
}
inline void FrameRectARGB(wil::unique_hdc& hdc, const RECT &rc, BYTE bAlpha, COLORREF clr, int thickness)
{
RECT sides[] = {
{ rc.left, rc.top, (rc.left + thickness), rc.bottom },
{ (rc.right - thickness), rc.top, rc.right, rc.bottom },
{ (rc.left + thickness), rc.top, (rc.right - thickness), (rc.top + thickness) },
{ (rc.left + thickness), (rc.bottom - thickness), (rc.right - thickness), rc.bottom }
};
for (UINT i = 0; i < ARRAYSIZE(sides); i++)
{
FillRectARGB(hdc, &(sides[i]), bAlpha, clr, false);
}
RGBQUAD bitmapBits;
InitRGB(&bitmapBits, alpha, color);
StretchDIBits(
hdc.get(),
fillRect.left,
fillRect.top,
fillRect.right - fillRect.left,
fillRect.bottom - fillRect.top,
0,
0,
1,
1,
&bitmapBits,
&bi,
DIB_RGB_COLORS,
SRCCOPY);
}
inline void ParseDeviceId(PCWSTR deviceId, PWSTR parsedId, size_t size)
{
// We're interested in the unique part between the first and last #'s
// Example input: \\?\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
// Example output: DELA026#5&10a58c63&0&UID16777488
// Example output: DELA026#5&10a58c63&0&UID16777488
const std::wstring defaultDeviceId = L"FallbackDevice";
if (!deviceId)
{
@@ -140,10 +111,10 @@ inline void ParseDeviceId(PCWSTR deviceId, PWSTR parsedId, size_t size)
}
}
inline unsigned char OpacitySettingToAlpha(int opacity)
inline BYTE OpacitySettingToAlpha(int opacity)
{
// convert percentage to a 0-255 alpha value
return static_cast<unsigned char>(opacity * 2.55);
return static_cast<BYTE>(opacity * 2.55);
}
UINT GetDpiForMonitor(HMONITOR monitor) noexcept;
UINT GetDpiForMonitor(HMONITOR monitor) noexcept;
void OrderMonitors(std::vector<std::pair<HMONITOR, RECT>>& monitorInfo);

View File

@@ -12,123 +12,43 @@ using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace FancyZonesUnitTests
{
TEST_CLASS(FancyZonesUnitTests)
TEST_CLASS (FancyZonesUnitTests)
{
HINSTANCE m_hInst;
winrt::com_ptr<IFancyZonesSettings> m_settings;
TEST_METHOD_INITIALIZE(Init)
{
m_hInst = (HINSTANCE)GetModuleHandleW(nullptr);
m_settings = MakeFancyZonesSettings(m_hInst, L"FancyZonesUnitTests");
Assert::IsTrue(m_settings != nullptr);
}
TEST_METHOD(Create)
{
auto actual = MakeFancyZones(m_hInst, m_settings);
Assert::IsNotNull(actual.get());
}
TEST_METHOD(CreateWithEmptyHinstance)
{
auto actual = MakeFancyZones({}, m_settings);
Assert::IsNotNull(actual.get());
}
TEST_METHOD(CreateWithNullHinstance)
{
auto actual = MakeFancyZones(nullptr, m_settings);
Assert::IsNotNull(actual.get());
}
TEST_METHOD(CreateWithNullSettings)
{
auto actual = MakeFancyZones(m_hInst, nullptr);
Assert::IsNull(actual.get());
}
TEST_METHOD(Run)
{
auto actual = MakeFancyZones(m_hInst, m_settings);
std::vector<std::thread> threads;
std::atomic<int> counter = 0;
const int expectedCount = 10;
auto runFunc = [&]() {
actual->Run();
counter++;
};
for (int i = 0; i < expectedCount; i++)
{
threads.push_back(std::thread(runFunc));
m_hInst = (HINSTANCE)GetModuleHandleW(nullptr);
m_settings = MakeFancyZonesSettings(m_hInst, L"FancyZonesUnitTests");
Assert::IsTrue(m_settings != nullptr);
}
for (auto& thread : threads)
TEST_METHOD (Create)
{
thread.join();
auto actual = MakeFancyZones(m_hInst, m_settings);
Assert::IsNotNull(actual.get());
}
TEST_METHOD (CreateWithEmptyHinstance)
{
auto actual = MakeFancyZones({}, m_settings);
Assert::IsNotNull(actual.get());
}
Assert::AreEqual(expectedCount, counter.load());
}
TEST_METHOD(Destroy)
{
auto actual = MakeFancyZones(m_hInst, m_settings);
std::vector<std::thread> threads;
std::atomic<int> counter = 0;
const int expectedCount = 10;
auto destroyFunc = [&]() {
actual->Destroy();
counter++;
};
for (int i = 0; i < expectedCount; i++)
TEST_METHOD (CreateWithNullHinstance)
{
threads.push_back(std::thread(destroyFunc));
auto actual = MakeFancyZones(nullptr, m_settings);
Assert::IsNotNull(actual.get());
}
for (auto& thread : threads)
TEST_METHOD (CreateWithNullSettings)
{
thread.join();
auto actual = MakeFancyZones(m_hInst, nullptr);
Assert::IsNull(actual.get());
}
Assert::AreEqual(expectedCount, counter.load());
}
TEST_METHOD(RunDestroy)
{
auto actual = MakeFancyZones(m_hInst, m_settings);
std::vector<std::thread> threads;
std::atomic<int> counter = 0;
const int expectedCount = 20;
auto func = [&]() {
auto idHash = std::hash<std::thread::id>()(std::this_thread::get_id());
bool run = (idHash % 2 == 0);
run ? actual->Run() : actual->Destroy();
counter++;
};
for (int i = 0; i < expectedCount; i++)
{
threads.push_back(std::thread(func));
}
for (auto& thread : threads)
{
thread.join();
}
Assert::AreEqual(expectedCount, counter.load());
}
};
TEST_CLASS(FancyZonesIZoneWindowHostUnitTests)
TEST_CLASS (FancyZonesIZoneWindowHostUnitTests)
{
HINSTANCE m_hInst{};
std::wstring m_settingsLocation = L"FancyZonesUnitTests";
@@ -148,7 +68,11 @@ namespace FancyZonesUnitTests
ptSettings.add_bool_toogle(L"fancyzones_virtualDesktopChange_moveWindows", IDS_SETTING_DESCRIPTION_VIRTUALDESKTOPCHANGE_MOVEWINDOWS, settings.virtualDesktopChange_moveWindows);
ptSettings.add_bool_toogle(L"fancyzones_appLastZone_moveWindows", IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS, settings.appLastZone_moveWindows);
ptSettings.add_bool_toogle(L"use_cursorpos_editor_startupscreen", IDS_SETTING_DESCRIPTION_USE_CURSORPOS_EDITOR_STARTUPSCREEN, settings.use_cursorpos_editor_startupscreen);
ptSettings.add_bool_toogle(L"fancyzones_show_on_all_monitors", IDS_SETTING_DESCRIPTION_SHOW_FANCY_ZONES_ON_ALL_MONITORS, settings.showZonesOnAllMonitors);
ptSettings.add_bool_toogle(L"fancyzones_makeDraggedWindowTransparent", IDS_SETTING_DESCRIPTION_MAKE_DRAGGED_WINDOW_TRANSPARENT, settings.makeDraggedWindowTransparent);
ptSettings.add_int_spinner(L"fancyzones_highlight_opacity", IDS_SETTINGS_HIGHLIGHT_OPACITY, settings.zoneHighlightOpacity, 0, 100, 1);
ptSettings.add_color_picker(L"fancyzones_zoneColor", IDS_SETTING_DESCRIPTION_ZONECOLOR, settings.zoneColor);
ptSettings.add_color_picker(L"fancyzones_zoneBorderColor", IDS_SETTING_DESCRIPTION_ZONE_BORDER_COLOR, settings.zoneBorderColor);
ptSettings.add_color_picker(L"fancyzones_zoneHighlightColor", IDS_SETTING_DESCRIPTION_ZONEHIGHLIGHTCOLOR, settings.zoneHightlightColor);
ptSettings.add_multiline_string(L"fancyzones_excluded_apps", IDS_SETTING_EXCLCUDED_APPS_DESCRIPTION, settings.excludedApps);
@@ -156,92 +80,185 @@ namespace FancyZonesUnitTests
}
TEST_METHOD_INITIALIZE(Init)
{
m_hInst = (HINSTANCE)GetModuleHandleW(nullptr);
m_settings = MakeFancyZonesSettings(m_hInst, m_settingsLocation.c_str());
Assert::IsTrue(m_settings != nullptr);
{
m_hInst = (HINSTANCE)GetModuleHandleW(nullptr);
m_settings = MakeFancyZonesSettings(m_hInst, m_settingsLocation.c_str());
Assert::IsTrue(m_settings != nullptr);
auto fancyZones = MakeFancyZones(m_hInst, m_settings);
Assert::IsTrue(fancyZones != nullptr);
auto fancyZones = MakeFancyZones(m_hInst, m_settings);
Assert::IsTrue(fancyZones != nullptr);
m_zoneWindowHost = fancyZones.as<IZoneWindowHost>();
Assert::IsTrue(m_zoneWindowHost != nullptr);
}
m_zoneWindowHost = fancyZones.as<IZoneWindowHost>();
Assert::IsTrue(m_zoneWindowHost != nullptr);
}
TEST_METHOD_CLEANUP(Cleanup)
{
auto settingsFolder = PTSettingsHelper::get_module_save_folder_location(m_settingsLocation);
const auto settingsFile = settingsFolder + L"\\settings.json";
std::filesystem::remove(settingsFile);
std::filesystem::remove(settingsFolder);
}
TEST_METHOD_CLEANUP(Cleanup)
{
auto settingsFolder = PTSettingsHelper::get_module_save_folder_location(m_settingsLocation);
const auto settingsFile = settingsFolder + L"\\settings.json";
std::filesystem::remove(settingsFile);
std::filesystem::remove(settingsFolder);
}
TEST_METHOD(GetZoneHighlightColor)
{
const auto expected = RGB(171, 175, 238);
const Settings settings{
.shiftDrag = true,
.displayChange_moveWindows = true,
.virtualDesktopChange_moveWindows = true,
.zoneSetChange_flashZones = false,
.zoneSetChange_moveWindows = true,
.overrideSnapHotkeys = false,
.appLastZone_moveWindows = true,
.use_cursorpos_editor_startupscreen = true,
.zoneHightlightColor = L"#abafee",
.zoneHighlightOpacity = 45,
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_OEM_3),
.excludedApps = L"app\r\napp2",
.excludedAppsArray = { L"APP", L"APP2" },
};
TEST_METHOD (GetZoneColor)
{
const auto expected = RGB(171, 175, 238);
const Settings settings{
.shiftDrag = true,
.displayChange_moveWindows = true,
.virtualDesktopChange_moveWindows = true,
.zoneSetChange_flashZones = false,
.zoneSetChange_moveWindows = true,
.overrideSnapHotkeys = false,
.appLastZone_moveWindows = true,
.use_cursorpos_editor_startupscreen = true,
.zoneColor = L"#abafee",
.zoneBorderColor = L"FAFAFA",
.zoneHightlightColor = L"#FAFAFA",
.zoneHighlightOpacity = 45,
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_OEM_3),
.excludedApps = L"app\r\napp2",
.excludedAppsArray = { L"APP", L"APP2" },
};
auto config = serializedPowerToySettings(settings);
m_settings->SetConfig(config.c_str());
auto config = serializedPowerToySettings(settings);
m_settings->SetConfig(config.c_str());
const auto actual = m_zoneWindowHost->GetZoneHighlightColor();
Assert::AreEqual(expected, actual);
}
const auto actual = m_zoneWindowHost->GetZoneColor();
Assert::AreEqual(expected, actual);
}
TEST_METHOD(GetZoneHighlightOpacity)
{
const auto expected = 88;
const Settings settings{
.shiftDrag = true,
.displayChange_moveWindows = true,
.virtualDesktopChange_moveWindows = true,
.zoneSetChange_flashZones = false,
.zoneSetChange_moveWindows = true,
.overrideSnapHotkeys = false,
.appLastZone_moveWindows = true,
.use_cursorpos_editor_startupscreen = true,
.zoneHightlightColor = L"#abafee",
.zoneHighlightOpacity = expected,
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_OEM_3),
.excludedApps = L"app\r\napp2",
.excludedAppsArray = { L"APP", L"APP2" },
};
TEST_METHOD (GetZoneBorderColor)
{
const auto expected = RGB(171, 175, 238);
const Settings settings{
.shiftDrag = true,
.displayChange_moveWindows = true,
.virtualDesktopChange_moveWindows = true,
.zoneSetChange_flashZones = false,
.zoneSetChange_moveWindows = true,
.overrideSnapHotkeys = false,
.appLastZone_moveWindows = true,
.use_cursorpos_editor_startupscreen = true,
.zoneColor = L"#FAFAFA",
.zoneBorderColor = L"#abafee",
.zoneHightlightColor = L"#FAFAFA",
.zoneHighlightOpacity = 45,
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_OEM_3),
.excludedApps = L"app\r\napp2",
.excludedAppsArray = { L"APP", L"APP2" },
};
auto config = serializedPowerToySettings(settings);
m_settings->SetConfig(config.c_str());
auto config = serializedPowerToySettings(settings);
m_settings->SetConfig(config.c_str());
const auto actual = m_zoneWindowHost->GetZoneHighlightOpacity();
Assert::AreEqual(expected, actual);
}
const auto actual = m_zoneWindowHost->GetZoneBorderColor();
Assert::AreEqual(expected, actual);
}
TEST_METHOD(GetCurrentMonitorZoneSetEmpty)
{
const auto* actual = m_zoneWindowHost->GetParentZoneWindow(Mocks::Monitor());
Assert::IsNull(actual);
}
TEST_METHOD (GetZoneHighlightColor)
{
const auto expected = RGB(171, 175, 238);
const Settings settings{
.shiftDrag = true,
.displayChange_moveWindows = true,
.virtualDesktopChange_moveWindows = true,
.zoneSetChange_flashZones = false,
.zoneSetChange_moveWindows = true,
.overrideSnapHotkeys = false,
.appLastZone_moveWindows = true,
.use_cursorpos_editor_startupscreen = true,
.showZonesOnAllMonitors = false,
.makeDraggedWindowTransparent = true,
.zoneColor = L"#FAFAFA",
.zoneBorderColor = L"FAFAFA",
.zoneHightlightColor = L"#abafee",
.zoneHighlightOpacity = 45,
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_OEM_3),
.excludedApps = L"app\r\napp2",
.excludedAppsArray = { L"APP", L"APP2" },
};
TEST_METHOD(GetCurrentMonitorZoneSetNullMonitor)
{
const auto* actual = m_zoneWindowHost->GetParentZoneWindow(nullptr);
Assert::IsNull(actual);
}
auto config = serializedPowerToySettings(settings);
m_settings->SetConfig(config.c_str());
const auto actual = m_zoneWindowHost->GetZoneHighlightColor();
Assert::AreEqual(expected, actual);
}
TEST_METHOD (GetZoneHighlightOpacity)
{
const auto expected = 88;
const Settings settings{
.shiftDrag = true,
.displayChange_moveWindows = true,
.virtualDesktopChange_moveWindows = true,
.zoneSetChange_flashZones = false,
.zoneSetChange_moveWindows = true,
.overrideSnapHotkeys = false,
.appLastZone_moveWindows = true,
.use_cursorpos_editor_startupscreen = true,
.showZonesOnAllMonitors = false,
.makeDraggedWindowTransparent = true,
.zoneColor = L"#FAFAFA",
.zoneBorderColor = L"FAFAFA",
.zoneHightlightColor = L"#abafee",
.zoneHighlightOpacity = expected,
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_OEM_3),
.excludedApps = L"app\r\napp2",
.excludedAppsArray = { L"APP", L"APP2" },
};
auto config = serializedPowerToySettings(settings);
m_settings->SetConfig(config.c_str());
const auto actual = m_zoneWindowHost->GetZoneHighlightOpacity();
Assert::AreEqual(expected, actual);
}
TEST_METHOD (IsMakeDraggenWindowTransparentActive)
{
const auto expected = true;
const Settings settings{
.shiftDrag = true,
.displayChange_moveWindows = true,
.virtualDesktopChange_moveWindows = true,
.zoneSetChange_flashZones = false,
.zoneSetChange_moveWindows = true,
.overrideSnapHotkeys = false,
.appLastZone_moveWindows = true,
.use_cursorpos_editor_startupscreen = true,
.showZonesOnAllMonitors = false,
.makeDraggedWindowTransparent = true,
.zoneColor = L"#FAFAFA",
.zoneBorderColor = L"FAFAFA",
.zoneHightlightColor = L"#abafee",
.zoneHighlightOpacity = expected,
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_OEM_3),
.excludedApps = L"app\r\napp2",
.excludedAppsArray = { L"APP", L"APP2" },
};
auto config = serializedPowerToySettings(settings);
m_settings->SetConfig(config.c_str());
Assert::AreEqual(expected, m_zoneWindowHost->isMakeDraggedWindowTransparentActive());
}
TEST_METHOD (GetCurrentMonitorZoneSetEmpty)
{
const auto* actual = m_zoneWindowHost->GetParentZoneWindow(Mocks::Monitor());
Assert::IsNull(actual);
}
TEST_METHOD (GetCurrentMonitorZoneSetNullMonitor)
{
const auto* actual = m_zoneWindowHost->GetParentZoneWindow(nullptr);
Assert::IsNull(actual);
}
};
TEST_CLASS(FancyZonesIFancyZonesCallbackUnitTests)
TEST_CLASS (FancyZonesIFancyZonesCallbackUnitTests)
{
HINSTANCE m_hInst{};
std::wstring m_settingsLocation = L"FancyZonesUnitTests";
@@ -263,7 +280,11 @@ namespace FancyZonesUnitTests
ptSettings.add_bool_toogle(L"fancyzones_virtualDesktopChange_moveWindows", IDS_SETTING_DESCRIPTION_VIRTUALDESKTOPCHANGE_MOVEWINDOWS, settings.virtualDesktopChange_moveWindows);
ptSettings.add_bool_toogle(L"fancyzones_appLastZone_moveWindows", IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS, settings.appLastZone_moveWindows);
ptSettings.add_bool_toogle(L"use_cursorpos_editor_startupscreen", IDS_SETTING_DESCRIPTION_USE_CURSORPOS_EDITOR_STARTUPSCREEN, settings.use_cursorpos_editor_startupscreen);
ptSettings.add_bool_toogle(L"fancyzones_show_on_all_monitors", IDS_SETTING_DESCRIPTION_SHOW_FANCY_ZONES_ON_ALL_MONITORS, settings.showZonesOnAllMonitors);
ptSettings.add_bool_toogle(L"fancyzones_makeDraggedWindowTransparent", IDS_SETTING_DESCRIPTION_MAKE_DRAGGED_WINDOW_TRANSPARENT, settings.makeDraggedWindowTransparent);
ptSettings.add_int_spinner(L"fancyzones_highlight_opacity", IDS_SETTINGS_HIGHLIGHT_OPACITY, settings.zoneHighlightOpacity, 0, 100, 1);
ptSettings.add_color_picker(L"fancyzones_zoneColor", IDS_SETTING_DESCRIPTION_ZONECOLOR, settings.zoneColor);
ptSettings.add_color_picker(L"fancyzones_zoneBorderColor", IDS_SETTING_DESCRIPTION_ZONE_BORDER_COLOR, settings.zoneBorderColor);
ptSettings.add_color_picker(L"fancyzones_zoneHighlightColor", IDS_SETTING_DESCRIPTION_ZONEHIGHLIGHTCOLOR, settings.zoneHightlightColor);
ptSettings.add_multiline_string(L"fancyzones_excluded_apps", IDS_SETTING_EXCLCUDED_APPS_DESCRIPTION, settings.excludedApps);
@@ -283,157 +304,158 @@ namespace FancyZonesUnitTests
}
TEST_METHOD_INITIALIZE(Init)
{
m_hInst = (HINSTANCE)GetModuleHandleW(nullptr);
m_settings = MakeFancyZonesSettings(m_hInst, m_settingsLocation.c_str());
Assert::IsTrue(m_settings != nullptr);
auto fancyZones = MakeFancyZones(m_hInst, m_settings);
Assert::IsTrue(fancyZones != nullptr);
m_fzCallback = fancyZones.as<IFancyZonesCallback>();
Assert::IsTrue(m_fzCallback != nullptr);
m_fancyZonesData.clear_data();
}
TEST_METHOD_CLEANUP(Cleanup)
{
sendKeyboardInput(VK_SHIFT, true);
sendKeyboardInput(VK_LWIN, true);
sendKeyboardInput(VK_CONTROL, true);
auto settingsFolder = PTSettingsHelper::get_module_save_folder_location(m_settingsLocation);
const auto settingsFile = settingsFolder + L"\\settings.json";
std::filesystem::remove(settingsFile);
std::filesystem::remove(settingsFolder);
}
TEST_METHOD(OnKeyDownNothingPressed)
{
for (DWORD code = '0'; code <= '9'; code++)
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = code;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
m_hInst = (HINSTANCE)GetModuleHandleW(nullptr);
m_settings = MakeFancyZonesSettings(m_hInst, m_settingsLocation.c_str());
Assert::IsTrue(m_settings != nullptr);
auto fancyZones = MakeFancyZones(m_hInst, m_settings);
Assert::IsTrue(fancyZones != nullptr);
m_fzCallback = fancyZones.as<IFancyZonesCallback>();
Assert::IsTrue(m_fzCallback != nullptr);
m_fancyZonesData.clear_data();
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_LEFT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
TEST_METHOD_CLEANUP(Cleanup)
{
sendKeyboardInput(VK_SHIFT, true);
sendKeyboardInput(VK_LWIN, true);
sendKeyboardInput(VK_CONTROL, true);
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_RIGHT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
}
auto settingsFolder = PTSettingsHelper::get_module_save_folder_location(m_settingsLocation);
const auto settingsFile = settingsFolder + L"\\settings.json";
std::filesystem::remove(settingsFile);
std::filesystem::remove(settingsFolder);
}
TEST_METHOD(OnKeyDownShiftPressed)
{
sendKeyboardInput(VK_SHIFT);
TEST_METHOD (OnKeyDownNothingPressed)
{
for (DWORD code = '0'; code <= '9'; code++)
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = code;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
for (DWORD code = '0'; code <= '9'; code++)
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = code;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_LEFT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_LEFT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_RIGHT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_RIGHT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
}
TEST_METHOD (OnKeyDownShiftPressed)
{
sendKeyboardInput(VK_SHIFT);
TEST_METHOD(OnKeyDownWinPressed)
{
sendKeyboardInput(VK_LWIN);
for (DWORD code = '0'; code <= '9'; code++)
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = code;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
for (DWORD code = '0'; code <= '9'; code++)
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = code;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_LEFT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_LEFT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_RIGHT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_RIGHT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
}
TEST_METHOD (OnKeyDownWinPressed)
{
sendKeyboardInput(VK_LWIN);
TEST_METHOD(OnKeyDownWinShiftPressed)
{
sendKeyboardInput(VK_LWIN);
sendKeyboardInput(VK_SHIFT);
for (DWORD code = '0'; code <= '9'; code++)
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = code;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
for (DWORD code = '0'; code <= '9'; code++)
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = code;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_LEFT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_LEFT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_RIGHT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_RIGHT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
}
TEST_METHOD (OnKeyDownWinShiftPressed)
{
sendKeyboardInput(VK_LWIN);
sendKeyboardInput(VK_SHIFT);
TEST_METHOD(OnKeyDownWinCtrlPressed)
{
sendKeyboardInput(VK_LWIN);
sendKeyboardInput(VK_CONTROL);
for (DWORD code = '0'; code <= '9'; code++)
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = code;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
const Settings settings{
.overrideSnapHotkeys = false,
};
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_LEFT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
auto config = serializedPowerToySettings(settings);
m_settings->SetConfig(config.c_str());
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_RIGHT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
}
/*
TEST_METHOD (OnKeyDownWinCtrlPressed)
{
sendKeyboardInput(VK_LWIN);
sendKeyboardInput(VK_CONTROL);
for (DWORD code = '0'; code <= '9'; code++)
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = code;
Assert::IsTrue(m_fzCallback->OnKeyDown(&input));
}
const Settings settings{
.overrideSnapHotkeys = false,
};
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_LEFT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
auto config = serializedPowerToySettings(settings);
m_settings->SetConfig(config.c_str());
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_RIGHT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
}
for (DWORD code = '0'; code <= '9'; code++)
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = code;
Assert::IsTrue(m_fzCallback->OnKeyDown(&input));
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_LEFT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_RIGHT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
}
*/
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -49,9 +49,13 @@
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>

View File

@@ -1,32 +1,235 @@
#include "pch.h"
#include "Util.h"
#include "lib\util.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace FancyZonesUnitTests
{
TEST_CLASS(UtilUnitTests){
public:
TEST_METHOD(TestParseDeviceId){
// We're interested in the unique part between the first and last #'s
// Example input: \\?\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
// Example output: DELA026#5&10a58c63&0&UID16777488
PCWSTR input = L"\\\\?\\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}";
wchar_t output[256]{};
ParseDeviceId(input, output, ARRAYSIZE(output));
Assert::AreEqual(0, wcscmp(output, L"DELA026#5&10a58c63&0&UID16777488"));
void TestMonitorSetPermutations(const std::vector<std::pair<HMONITOR, RECT>>& monitorInfo)
{
auto monitorInfoPermutation = monitorInfo;
do {
auto monitorInfoCopy = monitorInfoPermutation;
OrderMonitors(monitorInfoCopy);
CustomAssert::AreEqual(monitorInfo, monitorInfoCopy);
} while (std::next_permutation(monitorInfoPermutation.begin(), monitorInfoPermutation.end(), [](auto x, auto y) { return x.first < y.first; }));
}
void TestMonitorSetPermutationsOffsets(const std::vector<std::pair<HMONITOR, RECT>>& monitorInfo)
{
for (int offsetX = -3000; offsetX <= 3000; offsetX += 1000)
{
for (int offsetY = -3000; offsetY <= 3000; offsetY += 1000)
{
auto monitorInfoCopy = monitorInfo;
for (auto& [monitor, rect] : monitorInfoCopy)
{
rect.left += offsetX;
rect.right += offsetX;
rect.top += offsetY;
rect.bottom += offsetY;
}
TestMonitorSetPermutations(monitorInfoCopy);
}
}
}
TEST_CLASS(UtilUnitTests)
{
TEST_METHOD(TestParseDeviceId)
{
// We're interested in the unique part between the first and last #'s
// Example input: \\?\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
// Example output: DELA026#5&10a58c63&0&UID16777488
PCWSTR input = L"\\\\?\\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}";
wchar_t output[256]{};
ParseDeviceId(input, output, ARRAYSIZE(output));
Assert::AreEqual(0, wcscmp(output, L"DELA026#5&10a58c63&0&UID16777488"));
}
TEST_METHOD(TestParseInvalidDeviceId)
{
// We're interested in the unique part between the first and last #'s
// Example input: \\?\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
// Example output: DELA026#5&10a58c63&0&UID16777488
PCWSTR input = L"AnInvalidDeviceId";
wchar_t output[256]{};
ParseDeviceId(input, output, ARRAYSIZE(output));
Assert::AreEqual(0, wcscmp(output, L"FallbackDevice"));
}
TEST_METHOD(TestMonitorOrdering01)
{
// Three horizontally arranged monitors, bottom aligned, with increasing sizes
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
{Mocks::Monitor(), RECT{.left = 0, .top = 200, .right = 1600, .bottom = 1100} },
{Mocks::Monitor(), RECT{.left = 1600, .top = 100, .right = 3300, .bottom = 1100} },
{Mocks::Monitor(), RECT{.left = 3300, .top = 0, .right = 5100, .bottom = 1100} },
};
TestMonitorSetPermutationsOffsets(monitorInfo);
}
TEST_METHOD(TestMonitorOrdering02)
{
// Three horizontally arranged monitors, bottom aligned, with equal sizes
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
{Mocks::Monitor(), RECT{.left = 0, .top = 0, .right = 1600, .bottom = 900} },
{Mocks::Monitor(), RECT{.left = 1600, .top = 0, .right = 3200, .bottom = 900} },
{Mocks::Monitor(), RECT{.left = 3200, .top = 0, .right = 4800, .bottom = 900} },
};
TestMonitorSetPermutationsOffsets(monitorInfo);
}
TEST_METHOD(TestMonitorOrdering03)
{
// Three horizontally arranged monitors, bottom aligned, with decreasing sizes
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
{Mocks::Monitor(), RECT{.left = 0, .top = 0, .right = 1800, .bottom = 1100} },
{Mocks::Monitor(), RECT{.left = 1800, .top = 100, .right = 3500, .bottom = 1100} },
{Mocks::Monitor(), RECT{.left = 3500, .top = 200, .right = 5100, .bottom = 1100} },
};
TestMonitorSetPermutationsOffsets(monitorInfo);
}
TEST_METHOD(TestMonitorOrdering04)
{
// Three horizontally arranged monitors, top aligned, with increasing sizes
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
{Mocks::Monitor(), RECT{.left = 0, .top = 0, .right = 1600, .bottom = 900} },
{Mocks::Monitor(), RECT{.left = 1600, .top = 0, .right = 3300, .bottom = 1000} },
{Mocks::Monitor(), RECT{.left = 3300, .top = 0, .right = 5100, .bottom = 1100} },
};
TestMonitorSetPermutationsOffsets(monitorInfo);
}
TEST_METHOD(TestMonitorOrdering05)
{
// Three horizontally arranged monitors, top aligned, with equal sizes
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
{Mocks::Monitor(), RECT{.left = 0, .top = 0, .right = 1600, .bottom = 900} },
{Mocks::Monitor(), RECT{.left = 1600, .top = 0, .right = 3200, .bottom = 900} },
{Mocks::Monitor(), RECT{.left = 3200, .top = 0, .right = 4800, .bottom = 900} },
};
TestMonitorSetPermutationsOffsets(monitorInfo);
}
TEST_METHOD(TestMonitorOrdering06)
{
// Three horizontally arranged monitors, top aligned, with decreasing sizes
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
{Mocks::Monitor(), RECT{.left = 0, .top = 0, .right = 1800, .bottom = 1100} },
{Mocks::Monitor(), RECT{.left = 1800, .top = 0, .right = 3500, .bottom = 1000} },
{Mocks::Monitor(), RECT{.left = 3500, .top = 0, .right = 5100, .bottom = 900} },
};
TestMonitorSetPermutationsOffsets(monitorInfo);
}
TEST_METHOD(TestMonitorOrdering07)
{
// Three vertically arranged monitors, center aligned, with equal sizes, except the middle monitor is a bit wider
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
{Mocks::Monitor(), RECT{.left = 100, .top = 0, .right = 1700, .bottom = 900} },
{Mocks::Monitor(), RECT{.left = 0, .top = 900, .right = 1800, .bottom = 1800} },
{Mocks::Monitor(), RECT{.left = 100, .top = 1800, .right = 1700, .bottom = 2700} },
};
TestMonitorSetPermutationsOffsets(monitorInfo);
}
TEST_METHOD(TestMonitorOrdering08)
{
// ------------------
// | || || |
// | || || |
// ------------------
// | || |
// | || |
// ------------------
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
{Mocks::Monitor(), RECT{.left = 0, .top = 0, .right = 600, .bottom = 400} },
{Mocks::Monitor(), RECT{.left = 600, .top = 0, .right = 1200, .bottom = 400} },
{Mocks::Monitor(), RECT{.left = 1200, .top = 0, .right = 1800, .bottom = 400} },
{Mocks::Monitor(), RECT{.left = 0, .top = 400, .right = 900, .bottom = 800} },
{Mocks::Monitor(), RECT{.left = 900, .top = 400, .right = 1800, .bottom = 800} },
};
TestMonitorSetPermutationsOffsets(monitorInfo);
}
TEST_METHOD(TestMonitorOrdering09)
{
// Regular 3x3 grid
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
{Mocks::Monitor(), RECT{.left = 0, .top = 0, .right = 400, .bottom = 300} },
{Mocks::Monitor(), RECT{.left = 400, .top = 0, .right = 800, .bottom = 300} },
{Mocks::Monitor(), RECT{.left = 800, .top = 0, .right = 1200, .bottom = 300} },
{Mocks::Monitor(), RECT{.left = 0, .top = 300, .right = 400, .bottom = 600} },
{Mocks::Monitor(), RECT{.left = 400, .top = 300, .right = 800, .bottom = 600} },
{Mocks::Monitor(), RECT{.left = 800, .top = 300, .right = 1200, .bottom = 600} },
{Mocks::Monitor(), RECT{.left = 0, .top = 600, .right = 400, .bottom = 900} },
{Mocks::Monitor(), RECT{.left = 400, .top = 600, .right = 800, .bottom = 900} },
{Mocks::Monitor(), RECT{.left = 800, .top = 600, .right = 1200, .bottom = 900} },
};
// Reduce running time by testing only rotations
for (int i = 0; i < 9; i++)
{
auto monitorInfoCopy = monitorInfo;
std::rotate(monitorInfoCopy.begin(), monitorInfoCopy.begin() + i, monitorInfoCopy.end());
OrderMonitors(monitorInfoCopy);
CustomAssert::AreEqual(monitorInfo, monitorInfoCopy);
}
}
TEST_METHOD(TestMonitorOrdering10)
{
// ------------------
// | || |
// | || |
// ------------------
// | || || |
// | || || |
// ------------------
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
{Mocks::Monitor(), RECT{.left = 0, .top = 0, .right = 900, .bottom = 400} },
{Mocks::Monitor(), RECT{.left = 900, .top = 0, .right = 1800, .bottom = 400} },
{Mocks::Monitor(), RECT{.left = 0, .top = 400, .right = 600, .bottom = 800} },
{Mocks::Monitor(), RECT{.left = 600, .top = 400, .right = 1200, .bottom = 800} },
{Mocks::Monitor(), RECT{.left = 1200, .top = 400, .right = 1800, .bottom = 800} },
};
TestMonitorSetPermutationsOffsets(monitorInfo);
}
TEST_METHOD(TestMonitorOrdering11)
{
// Random values, some monitors overlap, don't check order, just ensure it doesn't crash and it's the same every time
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
{Mocks::Monitor(), RECT{.left = 410, .top = 630, .right = 988, .bottom = 631} },
{Mocks::Monitor(), RECT{.left = 302, .top = 189, .right = 550, .bottom = 714} },
{Mocks::Monitor(), RECT{.left = 158, .top = 115, .right = 657, .bottom = 499} },
{Mocks::Monitor(), RECT{.left = 341, .top = 340, .right = 723, .bottom = 655} },
{Mocks::Monitor(), RECT{.left = 433, .top = 393, .right = 846, .bottom = 544} },
};
auto monitorInfoPermutation = monitorInfo;
auto firstTime = monitorInfo;
OrderMonitors(firstTime);
do {
auto monitorInfoCopy = monitorInfoPermutation;
OrderMonitors(monitorInfoCopy);
CustomAssert::AreEqual(firstTime, monitorInfoCopy);
} while (next_permutation(monitorInfoPermutation.begin(), monitorInfoPermutation.end(), [](auto x, auto y) { return x.first < y.first; }));
}
};
}
TEST_METHOD(TestParseInvalidDeviceId)
{
// We're interested in the unique part between the first and last #'s
// Example input: \\?\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
// Example output: DELA026#5&10a58c63&0&UID16777488
PCWSTR input = L"AnInvalidDeviceId";
wchar_t output[256]{};
ParseDeviceId(input, output, ARRAYSIZE(output));
Assert::AreEqual(0, wcscmp(output, L"FallbackDevice"));
}
}
;
}

View File

@@ -146,4 +146,28 @@ namespace Mocks
m_conditionVar.notify_one();
}
}
}
std::wstring Helpers::GuidToString(const GUID& guid)
{
OLECHAR* guidString;
if (StringFromCLSID(guid, &guidString) == S_OK)
{
std::wstring guidStr{ guidString };
CoTaskMemFree(guidString);
return guidStr;
}
return L"";
}
std::wstring Helpers::CreateGuidString()
{
GUID guid;
if (CoCreateGuid(&guid) == S_OK)
{
return GuidToString(guid);
}
return L"";
}

View File

@@ -19,6 +19,15 @@ namespace CustomAssert
{
Microsoft::VisualStudio::CppUnitTestFramework::Assert::IsTrue(t1 == t2);
}
static void AreEqual(const std::vector<std::pair<HMONITOR, RECT>>& a1, const std::vector<std::pair<HMONITOR, RECT>>& a2)
{
Microsoft::VisualStudio::CppUnitTestFramework::Assert::IsTrue(a1.size() == a2.size());
for (size_t i = 0; i < a1.size(); i++)
{
Microsoft::VisualStudio::CppUnitTestFramework::Assert::IsTrue(a1[i].first == a2[i].first);
}
}
}
namespace Mocks
@@ -43,3 +52,9 @@ namespace Mocks
HWND WindowCreate(HINSTANCE hInst);
}
namespace Helpers
{
std::wstring GuidToString(const GUID& guid);
std::wstring CreateGuidString();
}

File diff suppressed because it is too large Load Diff

View File

@@ -18,6 +18,16 @@ namespace FancyZonesUnitTests
IFACEMETHODIMP_(void)
MoveWindowsOnActiveZoneSetChange() noexcept {};
IFACEMETHODIMP_(COLORREF)
GetZoneColor() noexcept
{
return RGB(0xFF, 0xFF, 0xFF);
}
IFACEMETHODIMP_(COLORREF)
GetZoneBorderColor() noexcept
{
return RGB(0xFF, 0xFF, 0xFF);
}
IFACEMETHODIMP_(COLORREF)
GetZoneHighlightColor() noexcept
{
return RGB(0xFF, 0xFF, 0xFF);
@@ -32,6 +42,11 @@ namespace FancyZonesUnitTests
{
return 100;
}
IFACEMETHODIMP_(bool)
isMakeDraggedWindowTransparentActive() noexcept
{
return true;
}
IZoneWindow* m_zoneWindow;
};
@@ -52,25 +67,6 @@ namespace FancyZonesUnitTests
JSONHelpers::FancyZonesData& m_fancyZonesData = JSONHelpers::FancyZonesDataInstance();
std::wstring GuidString(const GUID& guid)
{
OLECHAR* guidString;
Assert::AreEqual(S_OK, StringFromCLSID(guid, &guidString));
std::wstring guidStr{ guidString };
CoTaskMemFree(guidString);
return guidStr;
}
std::wstring CreateGuidString()
{
GUID guid;
Assert::AreEqual(S_OK, CoCreateGuid(&guid));
return GuidString(guid);
}
TEST_METHOD_INITIALIZE(Init)
{
m_hInst = (HINSTANCE)GetModuleHandleW(nullptr);
@@ -79,7 +75,7 @@ namespace FancyZonesUnitTests
m_monitorInfo.cbSize = sizeof(m_monitorInfo);
Assert::AreNotEqual(0, GetMonitorInfoW(m_monitor, &m_monitorInfo));
m_uniqueId << L"DELA026#5&10a58c63&0&UID16777488_" << m_monitorInfo.rcMonitor.right << "_" << m_monitorInfo.rcMonitor.bottom << "_MyVirtualDesktopId";
m_uniqueId << L"DELA026#5&10a58c63&0&UID16777488_" << m_monitorInfo.rcMonitor.right << "_" << m_monitorInfo.rcMonitor.bottom << "_{39B25DD2-130D-4B5D-8851-4791D66B1539}";
Assert::IsFalse(ZoneWindowUtils::GetActiveZoneSetTmpPath().empty());
Assert::IsFalse(ZoneWindowUtils::GetAppliedZoneSetTmpPath().empty());
@@ -108,7 +104,7 @@ namespace FancyZonesUnitTests
Assert::IsFalse(std::filesystem::exists(activeZoneSetTempPath));
const auto type = JSONHelpers::ZoneSetLayoutType::Columns;
const auto expectedZoneSet = JSONHelpers::ZoneSetData{ CreateGuidString(), type };
const auto expectedZoneSet = JSONHelpers::ZoneSetData{ Helpers::CreateGuidString(), type };
const auto data = JSONHelpers::DeviceInfoData{ expectedZoneSet, true, 16, 3 };
const auto deviceInfo = JSONHelpers::DeviceInfoJSON{ m_uniqueId.str(), data };
const auto json = JSONHelpers::DeviceInfoJSON::ToJson(deviceInfo);
@@ -208,7 +204,7 @@ namespace FancyZonesUnitTests
for (int type = static_cast<int>(ZoneSetLayoutType::Focus); type < static_cast<int>(ZoneSetLayoutType::Custom); type++)
{
const auto expectedZoneSet = ZoneSetData{ CreateGuidString(), static_cast<ZoneSetLayoutType>(type) };
const auto expectedZoneSet = ZoneSetData{ Helpers::CreateGuidString(), static_cast<ZoneSetLayoutType>(type) };
const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 };
const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data };
const auto json = DeviceInfoJSON::ToJson(deviceInfo);
@@ -232,7 +228,7 @@ namespace FancyZonesUnitTests
const auto activeZoneSetTempPath = ZoneWindowUtils::GetActiveZoneSetTmpPath();
const ZoneSetLayoutType type = ZoneSetLayoutType::Custom;
const auto expectedZoneSet = ZoneSetData{ CreateGuidString(), type };
const auto expectedZoneSet = ZoneSetData{ Helpers::CreateGuidString(), type };
const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 };
const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data };
const auto json = DeviceInfoJSON::ToJson(deviceInfo);
@@ -260,7 +256,7 @@ namespace FancyZonesUnitTests
const auto appliedZoneSetTempPath = ZoneWindowUtils::GetAppliedZoneSetTmpPath();
const ZoneSetLayoutType type = ZoneSetLayoutType::Custom;
const auto customSetGuid = CreateGuidString();
const auto customSetGuid = Helpers::CreateGuidString();
const auto expectedZoneSet = ZoneSetData{ customSetGuid, type };
const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 };
const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data };
@@ -297,7 +293,7 @@ namespace FancyZonesUnitTests
const auto deletedZonesTempPath = ZoneWindowUtils::GetCustomZoneSetsTmpPath();
const ZoneSetLayoutType type = ZoneSetLayoutType::Custom;
const auto customSetGuid = CreateGuidString();
const auto customSetGuid = Helpers::CreateGuidString();
const auto expectedZoneSet = ZoneSetData{ customSetGuid, type };
const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 };
const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data };
@@ -343,7 +339,7 @@ namespace FancyZonesUnitTests
const auto deletedZonesTempPath = ZoneWindowUtils::GetCustomZoneSetsTmpPath();
const ZoneSetLayoutType type = ZoneSetLayoutType::Custom;
const auto customSetGuid = CreateGuidString();
const auto customSetGuid = Helpers::CreateGuidString();
const auto expectedZoneSet = ZoneSetData{ customSetGuid, type };
const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 };
const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data };
@@ -361,7 +357,7 @@ namespace FancyZonesUnitTests
//save different zone as deleted
json::JsonObject deletedCustomZoneSets = {};
json::JsonArray zonesArray{};
const auto uuid = CreateGuidString();
const auto uuid = Helpers::CreateGuidString();
zonesArray.Append(json::JsonValue::CreateStringValue(uuid.substr(1, uuid.size() - 2).c_str()));
deletedCustomZoneSets.SetNamedValue(L"deleted-custom-zone-sets", zonesArray);
json::to_file(deletedZonesTempPath, deletedCustomZoneSets);
@@ -510,16 +506,6 @@ namespace FancyZonesUnitTests
Assert::AreNotEqual(-1, actualZoneIndex); //with invalid point zone remains the same
}
TEST_METHOD(MoveSizeCancel)
{
m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false);
const auto expected = S_OK;
const auto actual = m_zoneWindow->MoveSizeCancel();
Assert::AreEqual(expected, actual);
}
TEST_METHOD(MoveWindowIntoZoneByIndexNoActiveZoneSet)
{
m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false);
@@ -552,7 +538,7 @@ namespace FancyZonesUnitTests
Assert::IsNotNull(m_zoneWindow->ActiveZoneSet());
const auto window = Mocks::WindowCreate(m_hInst);
m_zoneWindow->MoveWindowIntoZoneByDirection(window, VK_RIGHT);
m_zoneWindow->MoveWindowIntoZoneByDirection(window, VK_RIGHT, true);
const auto actualAppZoneHistory = m_fancyZonesData.GetAppZoneHistoryMap();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
@@ -566,9 +552,9 @@ namespace FancyZonesUnitTests
Assert::IsNotNull(m_zoneWindow->ActiveZoneSet());
const auto window = Mocks::WindowCreate(m_hInst);
m_zoneWindow->MoveWindowIntoZoneByDirection(window, VK_RIGHT);
m_zoneWindow->MoveWindowIntoZoneByDirection(window, VK_RIGHT);
m_zoneWindow->MoveWindowIntoZoneByDirection(window, VK_RIGHT);
m_zoneWindow->MoveWindowIntoZoneByDirection(window, VK_RIGHT, true);
m_zoneWindow->MoveWindowIntoZoneByDirection(window, VK_RIGHT, true);
m_zoneWindow->MoveWindowIntoZoneByDirection(window, VK_RIGHT, true);
const auto actualAppZoneHistory = m_fancyZonesData.GetAppZoneHistoryMap();
Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
@@ -624,7 +610,7 @@ namespace FancyZonesUnitTests
const auto zoneSetId = m_zoneWindow->ActiveZoneSet()->Id();
//fill app zone history map
Assert::IsTrue(m_fancyZonesData.SetAppLastZone(window, deviceId, GuidString(zoneSetId), 0));
Assert::IsTrue(m_fancyZonesData.SetAppLastZone(window, deviceId, Helpers::GuidToString(zoneSetId), 0));
Assert::AreEqual((size_t)1, m_fancyZonesData.GetAppZoneHistoryMap().size());
Assert::AreEqual(0, m_fancyZonesData.GetAppZoneHistoryMap().at(processPath).zoneIndex);
@@ -652,7 +638,7 @@ namespace FancyZonesUnitTests
m_zoneWindow->ActiveZoneSet()->AddZone(zone);
//fill app zone history map
Assert::IsTrue(m_fancyZonesData.SetAppLastZone(window, deviceId, GuidString(zoneSetId), 2));
Assert::IsTrue(m_fancyZonesData.SetAppLastZone(window, deviceId, Helpers::GuidToString(zoneSetId), 2));
Assert::AreEqual((size_t)1, m_fancyZonesData.GetAppZoneHistoryMap().size());
Assert::AreEqual(2, m_fancyZonesData.GetAppZoneHistoryMap().at(processPath).zoneIndex);

View File

@@ -0,0 +1,29 @@
# Image Resizer
> A Windows Shell Extension for bulk image resizing
[**Overview**](#overview) ·
[**Settings**](#settings)
## Overview
Image Resizer is a windows shell extension for bulk image resizing. After installing PowerToys, right-click on one or more selected image files in File Explorer, and then select _Resize pictures_ from the menu.
![Image Resizer Demo](../../../doc/images/imageresizer/resizeNormal.gif)
Image Resizer also allows you to resize images by dragging and dropping your selected files with the right mouse button. This allows you to save your resized pictures in another folder.
![Image Resizer Drag And Drop Demo](../../../doc/images/imageresizer/resizeDragAndDrop.gif)
## Settings
![Image Resizer Settings](../../../doc/images/imageresizer/resizeSettings.gif)
Image Resizer allows the user to configure the following settings:
### Sizes
The user can add new preset sizes. Each size can be configured as Fill, Fit or Stretch. The dimension to be used for resizing can also be configured as Centimeters, Inches, Percent and Pixels.
### Encoding
The user can change the fallback encoder (the one it uses when it can't save as the original format) and modify PNG, JPEG and TIFF settings.
### File
The user can modify the format of the file name of the resized image. They can also choose to retain the original _last modified_ date on the resized image.

View File

@@ -0,0 +1,47 @@
// Copyright (c) Brice Lambson
// The Brice Lambson licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1009:ClosingParenthesisMustBeSpacedCorrectly", Justification = "All current violations are due to Tuple shorthand and so valid.")]
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:PrefixLocalCallsWithThis", Justification = "We follow the C# Core Coding Style which avoids using `this` unless absolutely necessary.")]
[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1200:UsingDirectivesMustBePlacedWithinNamespace", Justification = "We follow the C# Core Coding Style which puts using statements outside the namespace.")]
[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:ElementsMustAppearInTheCorrectOrder", Justification = "It is not a priority and have hight impact in code changes.")]
[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:ElementsMustBeOrderedByAccess", Justification = "It is not a priority and have hight impact in code changes.")]
[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1203:ConstantsMustAppearBeforeFields", Justification = "It is not a priority and have hight impact in code changes.")]
[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204:StaticElementsMustAppearBeforeInstanceElements", Justification = "It is not a priority and have hight impact in code changes.")]
[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1309:FieldNamesMustNotBeginWithUnderscore", Justification = "We follow the C# Core Coding Style which uses underscores as prefixes rather than using `this.`.")]
[assembly: SuppressMessage("StyleCop.CSharp.SpecialRules", "SA0001:XmlCommentAnalysisDisabled", Justification = "Not enabled as we don't want or need XML documentation.")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1629:DocumentationTextMustEndWithAPeriod", Justification = "Not enabled as we don't want or need XML documentation.")]
[assembly: SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly", Scope = "member", Target = "Microsoft.Templates.Core.Locations.TemplatesSynchronization.#SyncStatusChanged", Justification = "Using an Action<object, SyncStatusEventArgs> does not allow the required notation")]
// Non general supressions
[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "The WebBrowser is loading source code to be shown to the user. No localization required.", MessageId = "System.Windows.Controls.WebBrowser.NavigateToString(System.String)", Scope = "member", Target = "Microsoft.Templates.UI.Controls.CodeViewer.#UpdateCodeView(System.Func`2<System.String,System.String>,System.String,System.String,System.Boolean)")]
[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "This is part of the markdown processing", MessageId = "System.Windows.Documents.Run.#ctor(System.String)", Scope = "member", Target = "Microsoft.Templates.UI.Controls.Markdown.#ImageInlineEvaluator(System.Text.RegularExpressions.Match)")]
[assembly: SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "We need to have the names of these keys in lowercase to be able to compare with the keys becoming form the template json. ContainsKey does not allow StringComparer especification to IgnoreCase", Scope = "member", Target = "Microsoft.Templates.Core.ITemplateInfoExtensions.#GetQueryableProperties(Microsoft.TemplateEngine.Abstractions.ITemplateInfo)")]
[assembly: SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "We need to have the names of these keys in lowercase to be able to compare with the keys becoming form the template json. ContainsKey does not allow StringComparer especification to IgnoreCase", Scope = "member", Target = "Microsoft.Templates.Core.Composition.CompositionQuery.#Match(System.Collections.Generic.IEnumerable`1<Microsoft.Templates.Core.Composition.QueryNode>,Microsoft.Templates.Core.Composition.QueryablePropertyDictionary)")]
[assembly: SuppressMessage("Usage", "VSTHRD103:Call async methods when in an async method", Justification = "Resource DictionaryWriter does not implement flush async", Scope = "member", Target = "~M:Microsoft.Templates.Core.PostActions.Catalog.Merge.MergeResourceDictionaryPostAction.ExecuteInternalAsync~System.Threading.Tasks.Task")]
// Threading supressions
[assembly: SuppressMessage("Microsoft.VisualStudio.Threading.Analyzers", "VSTHRD100:Avoid async void methods", Justification = "Event handlers needs async void", Scope = "member", Target = "~M:Microsoft.Templates.UI.Controls.Notification.OnClose")]
[assembly: SuppressMessage("Microsoft.VisualStudio.Threading.Analyzers", "VSTHRD100:Avoid async void methods", Justification = "Event handlers needs async void", Scope = "member", Target = "~M:Microsoft.Templates.UI.ViewModels.Common.SavedTemplateViewModel.OnDelete")]
[assembly: SuppressMessage("Microsoft.VisualStudio.Threading.Analyzers", "VSTHRD100:Avoid async void methods", Justification = "Event handlers needs async void", Scope = "member", Target = "~M:Microsoft.Templates.UI.ViewModels.Common.WizardNavigation.GoBack")]
[assembly: SuppressMessage("Microsoft.VisualStudio.Threading.Analyzers", "VSTHRD100:Avoid async void methods", Justification = "Event handlers needs async void", Scope = "member", Target = "~M:Microsoft.Templates.UI.ViewModels.Common.WizardNavigation.GoForward")]
[assembly: SuppressMessage("Microsoft.VisualStudio.Threading.Analyzers", "VSTHRD100:Avoid async void methods", Justification = "Event handlers needs async void", Scope = "member", Target = "~M:Microsoft.Templates.UI.ViewModels.Common.SavedTemplateViewModel.OnDelete(Microsoft.Templates.UI.ViewModels.Common.SavedTemplateViewModel)")]
// Localization suppressions
[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.ThrowLastWin32Error(System.String)", Scope = "member", Target = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.#CreateJunction(System.String,System.String,System.Boolean)", Justification = "Only used for local generation")]
[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.ThrowLastWin32Error(System.String)", Scope = "member", Target = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.#DeleteJunction(System.String)", Justification = "Only used for local generation")]
[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.ThrowLastWin32Error(System.String)", Scope = "member", Target = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.#InternalGetTarget(Microsoft.Win32.SafeHandles.SafeFileHandle)", Justification = "Only used for local generation")]
[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.ThrowLastWin32Error(System.String)", Scope = "member", Target = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.#OpenReparsePoint(System.String,Microsoft.Templates.Core.Locations.JunctionNativeMethods+EFileAccess)", Justification = "Only used for local generation")]
[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Windows.Documents.InlineCollection.Add(System.String)", Scope = "member", Target = "Microsoft.Templates.UI.Extensions.TextBlockExtensions.#OnSequentialFlowStepChanged(System.Windows.DependencyObject,System.Windows.DependencyPropertyChangedEventArgs)", Justification = "No text here")]

View File

@@ -0,0 +1,407 @@
// ContextMenuHandler.cpp : Implementation of CContextMenuHandler
#include "stdafx.h"
#include "ContextMenuHandler.h"
#include "HDropIterator.h"
#include "Settings.h"
#include "common/icon_helpers.h"
#include "trace.h"
extern HINSTANCE g_hInst_imageResizer;
CContextMenuHandler::CContextMenuHandler()
{
m_pidlFolder = NULL;
m_pdtobj = NULL;
app_name = GET_RESOURCE_STRING(IDS_RESIZE_PICTURES);
}
CContextMenuHandler::~CContextMenuHandler()
{
Uninitialize();
}
void CContextMenuHandler::Uninitialize()
{
CoTaskMemFree((LPVOID)m_pidlFolder);
m_pidlFolder = NULL;
if (m_pdtobj)
{
m_pdtobj->Release();
m_pdtobj = NULL;
}
}
HRESULT CContextMenuHandler::Initialize(_In_opt_ PCIDLIST_ABSOLUTE pidlFolder, _In_opt_ IDataObject* pdtobj, _In_opt_ HKEY hkeyProgID)
{
Uninitialize();
if (!CSettings::GetEnabled())
{
return E_FAIL;
}
if (pidlFolder)
{
m_pidlFolder = ILClone(pidlFolder);
}
if (pdtobj)
{
m_pdtobj = pdtobj;
m_pdtobj->AddRef();
}
return S_OK;
}
HRESULT CContextMenuHandler::QueryContextMenu(_In_ HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
{
if (uFlags & CMF_DEFAULTONLY)
{
return S_OK;
}
if (!CSettings::GetEnabled())
{
return E_FAIL;
}
// NB: We just check the first item. We could iterate through more if the first one doesn't meet the criteria
HDropIterator i(m_pdtobj);
i.First();
// Suppressing C26812 warning as the issue is in the shtypes.h library
#pragma warning(suppress : 26812)
PERCEIVED type;
PERCEIVEDFLAG flag;
LPTSTR pszPath = i.CurrentItem();
LPTSTR pszExt = PathFindExtension(pszPath);
// TODO: Instead, detect whether there's a WIC codec installed that can handle this file
AssocGetPerceivedType(pszExt, &type, &flag, NULL);
free(pszPath);
bool dragDropFlag = false;
// If selected file is an image...
if (type == PERCEIVED_TYPE_IMAGE)
{
HRESULT hr = E_UNEXPECTED;
wchar_t strResizePictures[64] = { 0 };
// If handling drag-and-drop...
if (m_pidlFolder)
{
// Suppressing C6031 warning since return value is not required.
#pragma warning(suppress : 6031)
// Load 'Resize pictures here' string
LoadString(g_hInst_imageResizer, IDS_RESIZE_PICTURES_HERE, strResizePictures, ARRAYSIZE(strResizePictures));
dragDropFlag = true;
}
else
{
// Suppressing C6031 warning since return value is not required.
#pragma warning(suppress : 6031)
// Load 'Resize pictures' string
LoadString(g_hInst_imageResizer, IDS_RESIZE_PICTURES, strResizePictures, ARRAYSIZE(strResizePictures));
}
MENUITEMINFO mii;
mii.cbSize = sizeof(MENUITEMINFO);
mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID | MIIM_STATE;
mii.wID = idCmdFirst + ID_RESIZE_PICTURES;
mii.fType = MFT_STRING;
mii.dwTypeData = (PWSTR)strResizePictures;
mii.fState = MFS_ENABLED;
HICON hIcon = (HICON)LoadImage(g_hInst_imageResizer, MAKEINTRESOURCE(IDI_RESIZE_PICTURES), IMAGE_ICON, 16, 16, 0);
if (hIcon)
{
mii.fMask |= MIIM_BITMAP;
if (m_hbmpIcon == NULL)
{
m_hbmpIcon = CreateBitmapFromIcon(hIcon);
}
mii.hbmpItem = m_hbmpIcon;
DestroyIcon(hIcon);
}
if (dragDropFlag)
{
// Insert the menu entry at indexMenu+1 since the first entry should be "Copy here"
indexMenu++;
}
else
{
// indexMenu gets the first possible menu item index based on the location of the shellex registry key.
// If the registry entry is under SystemFileAssociations for the image formats, ShellImagePreview (in Windows by default) will be at indexMenu=0
// Shell ImagePreview consists of 4 menu items, a separator, Rotate right, Rotate left, and another separator
// Check if the entry at indexMenu is a separator, insert the new menu item at indexMenu+1 if true
MENUITEMINFO miiExisting;
miiExisting.dwTypeData = NULL;
miiExisting.fMask = MIIM_TYPE;
miiExisting.cbSize = sizeof(MENUITEMINFO);
GetMenuItemInfo(hmenu, indexMenu, TRUE, &miiExisting);
if (miiExisting.fType == MFT_SEPARATOR)
{
indexMenu++;
}
}
if (!InsertMenuItem(hmenu, indexMenu, TRUE, &mii))
{
hr = HRESULT_FROM_WIN32(GetLastError());
Trace::QueryContextMenuError(hr);
}
else
{
hr = MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 1);
}
return hr;
}
return S_OK;
}
HRESULT CContextMenuHandler::GetCommandString(UINT_PTR idCmd, UINT uType, _In_ UINT* pReserved, LPSTR pszName, UINT cchMax)
{
if (idCmd == ID_RESIZE_PICTURES)
{
if (uType == GCS_VERBW)
{
wcscpy_s((LPWSTR)pszName, cchMax, RESIZE_PICTURES_VERBW);
}
}
else
{
return E_INVALIDARG;
}
return S_OK;
}
HRESULT CContextMenuHandler::InvokeCommand(_In_ CMINVOKECOMMANDINFO* pici)
{
BOOL fUnicode = FALSE;
Trace::Invoked();
HRESULT hr = E_FAIL;
if (pici->cbSize == sizeof(CMINVOKECOMMANDINFOEX) && pici->fMask & CMIC_MASK_UNICODE)
{
fUnicode = TRUE;
}
if (!fUnicode && HIWORD(pici->lpVerb))
{
}
else if (fUnicode && HIWORD(((CMINVOKECOMMANDINFOEX*)pici)->lpVerbW))
{
if (wcscmp(((CMINVOKECOMMANDINFOEX*)pici)->lpVerbW, RESIZE_PICTURES_VERBW) == 0)
{
hr = ResizePictures(pici, nullptr);
}
}
else if (LOWORD(pici->lpVerb) == ID_RESIZE_PICTURES)
{
hr = ResizePictures(pici, nullptr);
}
Trace::InvokedRet(hr);
return hr;
}
// This function is used for both MSI and MSIX. If pici is null and psiItemArray is not null then this is called by Invoke(MSIX). If pici is not null and psiItemArray is null then this is called by InvokeCommand(MSI).
HRESULT CContextMenuHandler::ResizePictures(CMINVOKECOMMANDINFO* pici, IShellItemArray* psiItemArray)
{
// Set the application path based on the location of the dll
std::wstring path = get_module_folderpath(g_hInst_imageResizer);
path = path + L"\\ImageResizer.exe";
LPTSTR lpApplicationName = (LPTSTR)path.c_str();
// Create an anonymous pipe to stream filenames
SECURITY_ATTRIBUTES sa;
HANDLE hReadPipe;
HANDLE hWritePipe;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
HRESULT hr = E_FAIL;
if (!CreatePipe(&hReadPipe, &hWritePipe, &sa, 0))
{
hr = HRESULT_FROM_WIN32(GetLastError());
return hr;
}
if (!SetHandleInformation(hWritePipe, HANDLE_FLAG_INHERIT, 0))
{
hr = HRESULT_FROM_WIN32(GetLastError());
return hr;
}
CAtlFile writePipe(hWritePipe);
CString commandLine;
commandLine.Format(_T("\"%s\""), lpApplicationName);
// Set the output directory
if (m_pidlFolder)
{
TCHAR szFolder[MAX_PATH];
SHGetPathFromIDList(m_pidlFolder, szFolder);
commandLine.AppendFormat(_T(" /d \"%s\""), szFolder);
}
int nSize = commandLine.GetLength() + 1;
LPTSTR lpszCommandLine = new TCHAR[nSize];
_tcscpy_s(lpszCommandLine, nSize, commandLine);
STARTUPINFO startupInfo;
ZeroMemory(&startupInfo, sizeof(STARTUPINFO));
startupInfo.cb = sizeof(STARTUPINFO);
startupInfo.hStdInput = hReadPipe;
startupInfo.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
if (pici)
{
startupInfo.wShowWindow = pici->nShow;
}
else
{
startupInfo.wShowWindow = SW_SHOWNORMAL;
}
PROCESS_INFORMATION processInformation;
// Start the resizer
CreateProcess(
NULL,
lpszCommandLine,
NULL,
NULL,
TRUE,
0,
NULL,
NULL,
&startupInfo,
&processInformation);
delete[] lpszCommandLine;
if (!CloseHandle(processInformation.hProcess))
{
hr = HRESULT_FROM_WIN32(GetLastError());
return hr;
}
if (!CloseHandle(processInformation.hThread))
{
hr = HRESULT_FROM_WIN32(GetLastError());
return hr;
}
// psiItemArray is NULL if called from InvokeCommand. This part is used for the MSI installer. It is not NULL if it is called from Invoke (MSIX).
if (!psiItemArray)
{
// Stream the input files
HDropIterator i(m_pdtobj);
for (i.First(); !i.IsDone(); i.Next())
{
CString fileName(i.CurrentItem());
fileName.Append(_T("\r\n"));
writePipe.Write(fileName, fileName.GetLength() * sizeof(TCHAR));
}
}
else
{
//m_pdtobj will be NULL when invoked from the MSIX build as Initialize is never called (IShellExtInit functions aren't called in case of MSIX).
DWORD fileCount = 0;
// Gets the list of files currently selected using the IShellItemArray
psiItemArray->GetCount(&fileCount);
// Iterate over the list of files
for (DWORD i = 0; i < fileCount; i++)
{
IShellItem* shellItem;
psiItemArray->GetItemAt(i, &shellItem);
LPWSTR itemName;
// Retrieves the entire file system path of the file from its shell item
shellItem->GetDisplayName(SIGDN_FILESYSPATH, &itemName);
CString fileName(itemName);
fileName.Append(_T("\r\n"));
// Write the file path into the input stream for image resizer
writePipe.Write(fileName, fileName.GetLength() * sizeof(TCHAR));
}
}
writePipe.Close();
hr = S_OK;
return hr;
}
HRESULT __stdcall CContextMenuHandler::GetTitle(IShellItemArray* /*psiItemArray*/, LPWSTR* ppszName)
{
return SHStrDup(app_name.c_str(), ppszName);
}
HRESULT __stdcall CContextMenuHandler::GetIcon(IShellItemArray* /*psiItemArray*/, LPWSTR* ppszIcon)
{
// Since ImageResizer is registered as a COM SurrogateServer the current module filename would be dllhost.exe. To get the icon we need the path of ImageResizerExt.dll, which can be obtained by passing the HINSTANCE of the dll
std::wstring iconResourcePath = get_module_filename(g_hInst_imageResizer);
iconResourcePath += L",-";
iconResourcePath += std::to_wstring(IDI_RESIZE_PICTURES);
return SHStrDup(iconResourcePath.c_str(), ppszIcon);
}
HRESULT __stdcall CContextMenuHandler::GetToolTip(IShellItemArray* /*psiItemArray*/, LPWSTR* ppszInfotip)
{
*ppszInfotip = nullptr;
return E_NOTIMPL;
}
HRESULT __stdcall CContextMenuHandler::GetCanonicalName(GUID* pguidCommandName)
{
*pguidCommandName = __uuidof(this);
return S_OK;
}
HRESULT __stdcall CContextMenuHandler::GetState(IShellItemArray* psiItemArray, BOOL fOkToBeSlow, EXPCMDSTATE* pCmdState)
{
if (!CSettings::GetEnabled())
{
*pCmdState = ECS_HIDDEN;
return S_OK;
}
// Hide if the file is not an image
*pCmdState = ECS_HIDDEN;
// Suppressing C26812 warning as the issue is in the shtypes.h library
#pragma warning(suppress : 26812)
PERCEIVED type;
PERCEIVEDFLAG flag;
IShellItem* shellItem;
//Check extension of first item in the list (the item which is right-clicked on)
psiItemArray->GetItemAt(0, &shellItem);
LPTSTR pszPath;
// Retrieves the entire file system path of the file from its shell item
shellItem->GetDisplayName(SIGDN_FILESYSPATH, &pszPath);
LPTSTR pszExt = PathFindExtension(pszPath);
// TODO: Instead, detect whether there's a WIC codec installed that can handle this file
AssocGetPerceivedType(pszExt, &type, &flag, NULL);
CoTaskMemFree(pszPath);
// If selected file is an image...
if (type == PERCEIVED_TYPE_IMAGE)
{
*pCmdState = ECS_ENABLED;
}
return S_OK;
}
HRESULT __stdcall CContextMenuHandler::GetFlags(EXPCMDFLAGS* pFlags)
{
*pFlags = ECF_DEFAULT;
return S_OK;
}
HRESULT __stdcall CContextMenuHandler::EnumSubCommands(IEnumExplorerCommand** ppEnum)
{
*ppEnum = nullptr;
return E_NOTIMPL;
}
// psiItemArray contains the list of files that have been selected when the context menu entry is invoked
HRESULT __stdcall CContextMenuHandler::Invoke(IShellItemArray* psiItemArray, IBindCtx* /*pbc*/)
{
Trace::Invoked();
HRESULT hr = ResizePictures(nullptr, psiItemArray);
Trace::InvokedRet(hr);
return hr;
}

View File

@@ -0,0 +1,57 @@
#pragma once
#define ID_RESIZE_PICTURES 0
#define RESIZE_PICTURES_VERBW L"resize"
#include "stdafx.h"
#include "resource.h"
#include "ImageResizerExt_i.h"
#if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
#error "Single-threaded COM objects are not properly supported on Windows CE platform, such as the Windows Mobile platforms that do not include full DCOM support. Define _CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA to force ATL to support creating single-thread COM object's and allow use of it's single-threaded COM object implementations. The threading model in your rgs file was set to 'Free' as that is the only threading model supported in non DCOM Windows CE platforms."
#endif
using namespace ATL;
class ATL_NO_VTABLE __declspec(uuid("51B4D7E5-7568-4234-B4BB-47FB3C016A69")) CContextMenuHandler :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CContextMenuHandler, &CLSID_ContextMenuHandler>,
public IShellExtInit,
public IContextMenu,
public IExplorerCommand
{
BEGIN_COM_MAP(CContextMenuHandler)
COM_INTERFACE_ENTRY(IShellExtInit)
COM_INTERFACE_ENTRY(IContextMenu)
COM_INTERFACE_ENTRY(IExplorerCommand)
END_COM_MAP()
DECLARE_REGISTRY_RESOURCEID(IDR_CONTEXTMENUHANDLER)
DECLARE_NOT_AGGREGATABLE(CContextMenuHandler)
public:
CContextMenuHandler();
~CContextMenuHandler();
HRESULT STDMETHODCALLTYPE Initialize(_In_opt_ PCIDLIST_ABSOLUTE pidlFolder, _In_opt_ IDataObject* pdtobj, _In_opt_ HKEY hkeyProgID);
HRESULT STDMETHODCALLTYPE QueryContextMenu(_In_ HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags);
HRESULT STDMETHODCALLTYPE GetCommandString(UINT_PTR idCmd, UINT uType, _In_ UINT* pReserved, LPSTR pszName, UINT cchMax);
HRESULT STDMETHODCALLTYPE InvokeCommand(_In_ CMINVOKECOMMANDINFO* pici);
// Inherited via IExplorerCommand
virtual HRESULT __stdcall GetTitle(IShellItemArray* psiItemArray, LPWSTR* ppszName) override;
virtual HRESULT __stdcall GetIcon(IShellItemArray* psiItemArray, LPWSTR* ppszIcon) override;
virtual HRESULT __stdcall GetToolTip(IShellItemArray* psiItemArray, LPWSTR* ppszInfotip) override;
virtual HRESULT __stdcall GetCanonicalName(GUID* pguidCommandName) override;
virtual HRESULT __stdcall GetState(IShellItemArray* psiItemArray, BOOL fOkToBeSlow, EXPCMDSTATE* pCmdState) override;
virtual HRESULT __stdcall Invoke(IShellItemArray* psiItemArray, IBindCtx* pbc) override;
virtual HRESULT __stdcall GetFlags(EXPCMDFLAGS* pFlags) override;
virtual HRESULT __stdcall EnumSubCommands(IEnumExplorerCommand** ppEnum) override;
private:
void Uninitialize();
HRESULT ResizePictures(CMINVOKECOMMANDINFO* pici, IShellItemArray* psiItemArray);
PCIDLIST_ABSOLUTE m_pidlFolder;
IDataObject* m_pdtobj;
HBITMAP m_hbmpIcon = nullptr;
std::wstring app_name;
};
OBJECT_ENTRY_AUTO(__uuidof(ContextMenuHandler), CContextMenuHandler)

View File

@@ -0,0 +1,3 @@
HKCR
{
}

View File

@@ -0,0 +1,49 @@
#include "StdAfx.h"
#include "HDropIterator.h"
HDropIterator::HDropIterator(IDataObject* pdtobj)
{
_current = 0;
FORMATETC formatetc = {
CF_HDROP,
NULL,
DVASPECT_CONTENT,
-1,
TYMED_HGLOBAL
};
pdtobj->GetData(&formatetc, &m_medium);
_listCount = DragQueryFile((HDROP)m_medium.hGlobal, 0xFFFFFFFF, NULL, 0);
}
HDropIterator::~HDropIterator()
{
ReleaseStgMedium(&m_medium);
}
void HDropIterator::First()
{
_current = 0;
}
void HDropIterator::Next()
{
_current++;
}
bool HDropIterator::IsDone() const
{
return _current >= _listCount;
}
LPTSTR HDropIterator::CurrentItem() const
{
UINT cch = DragQueryFile((HDROP)m_medium.hGlobal, _current, NULL, 0) + 1;
LPTSTR pszPath = (LPTSTR)malloc(sizeof(TCHAR) * cch);
DragQueryFile((HDROP)m_medium.hGlobal, _current, pszPath, cch);
return pszPath;
}

View File

@@ -0,0 +1,17 @@
#pragma once
class HDropIterator
{
public:
HDropIterator(IDataObject *pDataObject);
~HDropIterator();
void First();
void Next();
bool IsDone() const;
LPTSTR CurrentItem() const;
private:
UINT _listCount;
STGMEDIUM m_medium;
UINT _current;
};

View File

@@ -0,0 +1,54 @@
#include "stdafx.h"
#include "resource.h"
#include "ImageResizerExt_i.h"
#include "dllmain.h"
__control_entrypoint(DllExport) STDAPI DllCanUnloadNow()
{
return _AtlModule.DllCanUnloadNow();
}
_Check_return_ STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _Outptr_ LPVOID* ppv)
{
return _AtlModule.DllGetClassObject(rclsid, riid, ppv);
}
STDAPI DllRegisterServer()
{
return _AtlModule.DllRegisterServer();
}
STDAPI DllUnregisterServer()
{
return _AtlModule.DllUnregisterServer();
}
STDAPI DllInstall(BOOL bInstall, _In_opt_ LPCWSTR pszCmdLine)
{
HRESULT hr = E_FAIL;
static const wchar_t szUserSwitch[] = L"user";
if (pszCmdLine != NULL)
{
if (_wcsnicmp(pszCmdLine, szUserSwitch, _countof(szUserSwitch)) == 0)
{
ATL::AtlSetPerUserRegistration(true);
}
}
if (bInstall)
{
hr = DllRegisterServer();
if (FAILED(hr))
{
DllUnregisterServer();
}
}
else
{
hr = DllUnregisterServer();
}
return hr;
}

View File

@@ -0,0 +1,8 @@
LIBRARY
EXPORTS
DllCanUnloadNow PRIVATE
DllGetClassObject PRIVATE
DllRegisterServer PRIVATE
DllUnregisterServer PRIVATE
DllInstall PRIVATE

View File

@@ -0,0 +1,15 @@
import "shobjidl.idl";
[
uuid(09082E28-A5CA-47A7-8571-A2236C411E91),
version(3.1)
]
library ImageResizerExtLib
{
[uuid(51B4D7E5-7568-4234-B4BB-47FB3C016A69)]
coclass ContextMenuHandler
{
[default] interface IShellExtInit;
interface IContextMenu;
};
};

Binary file not shown.

View File

@@ -0,0 +1,3 @@
HKCR
{
}

View File

@@ -0,0 +1,313 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{0B43679E-EDFA-4DA0-AD30-F4628B308B1B}</ProjectGuid>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<Keyword>AtlProj</Keyword>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<UseOfAtl>Static</UseOfAtl>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<UseOfAtl>Static</UseOfAtl>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<UseOfAtl>Static</UseOfAtl>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<UseOfAtl>Static</UseOfAtl>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<IgnoreImportLibrary>true</IgnoreImportLibrary>
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<IgnoreImportLibrary>true</IgnoreImportLibrary>
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<IgnoreImportLibrary>true</IgnoreImportLibrary>
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<IgnoreImportLibrary>true</IgnoreImportLibrary>
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_WINDOWS;_DEBUG;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<AdditionalIncludeDirectories>..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Midl>
<MkTypLibCompatible>false</MkTypLibCompatible>
<TargetEnvironment>Win32</TargetEnvironment>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<HeaderFileName>ImageResizerExt_i.h</HeaderFileName>
<InterfaceIdentifierFileName>ImageResizerExt_i.c</InterfaceIdentifierFileName>
<ProxyFileName>ImageResizerExt_p.c</ProxyFileName>
<GenerateStublessProxies>true</GenerateStublessProxies>
<TypeLibraryName>$(IntDir)ImageResizerExt.tlb</TypeLibraryName>
<DllDataFileName />
<ValidateAllParameters>true</ValidateAllParameters>
</Midl>
<ResourceCompile>
<Culture>0x0409</Culture>
<AdditionalIncludeDirectories>$(IntDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ResourceCompile>
<Link>
<SubSystem>Windows</SubSystem>
<ModuleDefinitionFile>.\ImageResizerExt.def</ModuleDefinitionFile>
<GenerateDebugInformation>true</GenerateDebugInformation>
<RegisterOutput>true</RegisterOutput>
<PerUserRedirection>true</PerUserRedirection>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>_WINDOWS;_DEBUG;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<AdditionalIncludeDirectories>..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;..\..\..\..\deps\cpprestsdk\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Midl>
<MkTypLibCompatible>false</MkTypLibCompatible>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<HeaderFileName>ImageResizerExt_i.h</HeaderFileName>
<InterfaceIdentifierFileName>ImageResizerExt_i.c</InterfaceIdentifierFileName>
<ProxyFileName>ImageResizerExt_p.c</ProxyFileName>
<GenerateStublessProxies>true</GenerateStublessProxies>
<TypeLibraryName>$(IntDir)ImageResizerExt.tlb</TypeLibraryName>
<DllDataFileName />
<ValidateAllParameters>true</ValidateAllParameters>
</Midl>
<ResourceCompile>
<Culture>0x0409</Culture>
<AdditionalIncludeDirectories>$(IntDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ResourceCompile>
<Link>
<SubSystem>Windows</SubSystem>
<ModuleDefinitionFile>.\ImageResizerExt.def</ModuleDefinitionFile>
<GenerateDebugInformation>true</GenerateDebugInformation>
<RegisterOutput>true</RegisterOutput>
<PerUserRedirection>true</PerUserRedirection>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<PreprocessorDefinitions>WIN32;_WINDOWS;NDEBUG;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<AdditionalIncludeDirectories>..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Midl>
<MkTypLibCompatible>false</MkTypLibCompatible>
<TargetEnvironment>Win32</TargetEnvironment>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<HeaderFileName>ImageResizerExt_i.h</HeaderFileName>
<InterfaceIdentifierFileName>ImageResizerExt_i.c</InterfaceIdentifierFileName>
<ProxyFileName>ImageResizerExt_p.c</ProxyFileName>
<GenerateStublessProxies>true</GenerateStublessProxies>
<TypeLibraryName>$(IntDir)ImageResizerExt.tlb</TypeLibraryName>
<DllDataFileName />
<ValidateAllParameters>true</ValidateAllParameters>
</Midl>
<ResourceCompile>
<Culture>0x0409</Culture>
<AdditionalIncludeDirectories>$(IntDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ResourceCompile>
<Link>
<SubSystem>Windows</SubSystem>
<ModuleDefinitionFile>.\ImageResizerExt.def</ModuleDefinitionFile>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<RegisterOutput>true</RegisterOutput>
<PerUserRedirection>true</PerUserRedirection>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<PreprocessorDefinitions>_WINDOWS;NDEBUG;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<AdditionalIncludeDirectories>..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;..\..\..\..\deps\cpprestsdk\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Midl>
<MkTypLibCompatible>false</MkTypLibCompatible>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<HeaderFileName>ImageResizerExt_i.h</HeaderFileName>
<InterfaceIdentifierFileName>ImageResizerExt_i.c</InterfaceIdentifierFileName>
<ProxyFileName>ImageResizerExt_p.c</ProxyFileName>
<GenerateStublessProxies>true</GenerateStublessProxies>
<TypeLibraryName>$(IntDir)ImageResizerExt.tlb</TypeLibraryName>
<DllDataFileName />
<ValidateAllParameters>true</ValidateAllParameters>
</Midl>
<ResourceCompile>
<Culture>0x0409</Culture>
<AdditionalIncludeDirectories>$(IntDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ResourceCompile>
<Link>
<SubSystem>Windows</SubSystem>
<ModuleDefinitionFile>.\ImageResizerExt.def</ModuleDefinitionFile>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<RegisterOutput>true</RegisterOutput>
<PerUserRedirection>true</PerUserRedirection>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="ContextMenuHandler.cpp" />
<ClCompile Include="HDropIterator.cpp" />
<ClCompile Include="dllmain.cpp">
<CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</CompileAsManaged>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
</PrecompiledHeader>
<CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</CompileAsManaged>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
</PrecompiledHeader>
<CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</CompileAsManaged>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
</PrecompiledHeader>
<CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</CompileAsManaged>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
</PrecompiledHeader>
</ClCompile>
<ClCompile Include="ImageResizerExt.cpp" />
<ClCompile Include="ImageResizerExt_i.c">
<CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</CompileAsManaged>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
</PrecompiledHeader>
<CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</CompileAsManaged>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
</PrecompiledHeader>
<CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</CompileAsManaged>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
</PrecompiledHeader>
<CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</CompileAsManaged>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
</PrecompiledHeader>
</ClCompile>
<ClCompile Include="Settings.cpp" />
<ClCompile Include="stdafx.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="trace.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="ContextMenuHandler.h" />
<ClInclude Include="HDropIterator.h" />
<ClInclude Include="dllmain.h" />
<ClInclude Include="Settings.h" />
<ClInclude Include="Resource.h" />
<ClInclude Include="ImageResizerExt_i.h" />
<ClInclude Include="stdafx.h" />
<ClInclude Include="targetver.h" />
<ClInclude Include="trace.h" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="ImageResizerExt.rc" />
</ItemGroup>
<ItemGroup>
<None Include="ContextMenuHandler.rgs" />
<None Include="ImageResizerExt.def" />
<None Include="ImageResizerExt.rgs" />
</ItemGroup>
<ItemGroup>
<Midl Include="ImageResizerExt.idl" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\common.vcxproj">
<Project>{74485049-c722-400f-abe5-86ac52d929b3}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Image Include="..\ui\Resources\ImageResizer.ico" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="Generated Files">
<UniqueIdentifier>{87bc9b81-f7fa-45d9-87cc-c99e55473868}</UniqueIdentifier>
<SourceControlFiles>False</SourceControlFiles>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="stdafx.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ImageResizerExt.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="dllmain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ImageResizerExt_i.c">
<Filter>Generated Files</Filter>
</ClCompile>
<ClCompile Include="ContextMenuHandler.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="HDropIterator.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Settings.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="trace.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClInclude Include="trace.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClInclude Include="stdafx.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="targetver.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Resource.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="dllmain.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ImageResizerExt_i.h">
<Filter>Generated Files</Filter>
</ClInclude>
<ClInclude Include="ContextMenuHandler.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="HDropIterator.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Settings.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="ImageResizerExt.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<None Include="ImageResizerExt.rgs">
<Filter>Resource Files</Filter>
</None>
<None Include="ImageResizerExt.def">
<Filter>Source Files</Filter>
</None>
<None Include="ContextMenuHandler.rgs">
<Filter>Resource Files</Filter>
</None>
</ItemGroup>
<ItemGroup>
<Midl Include="ImageResizerExt.idl">
<Filter>Source Files</Filter>
</Midl>
</ItemGroup>
<ItemGroup>
<Image Include="..\ui\Resources\ImageResizer.ico">
<Filter>Resource Files</Filter>
</Image>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,66 @@
#include "stdafx.h"
#include <commctrl.h>
#include "Settings.h"
const wchar_t c_rootRegPath[] = L"Software\\Microsoft\\ImageResizer";
const wchar_t c_enabled[] = L"Enabled";
const bool c_enabledDefault = true;
bool CSettings::GetEnabled()
{
return GetRegBoolValue(c_enabled, c_enabledDefault);
}
bool CSettings::SetEnabled(_In_ bool enabled)
{
return SetRegBoolValue(c_enabled, enabled);
}
bool CSettings::SetRegBoolValue(_In_ PCWSTR valueName, _In_ bool value)
{
DWORD dwValue = value ? 1 : 0;
return SetRegDWORDValue(valueName, dwValue);
}
bool CSettings::GetRegBoolValue(_In_ PCWSTR valueName, _In_ bool defaultValue)
{
DWORD value = GetRegDWORDValue(valueName, (defaultValue == 0) ? false : true);
return (value == 0) ? false : true;
}
bool CSettings::SetRegDWORDValue(_In_ PCWSTR valueName, _In_ DWORD value)
{
return (SUCCEEDED(HRESULT_FROM_WIN32(SHSetValue(HKEY_CURRENT_USER, c_rootRegPath, valueName, REG_DWORD, &value, sizeof(value)))));
}
DWORD CSettings::GetRegDWORDValue(_In_ PCWSTR valueName, _In_ DWORD defaultValue)
{
DWORD retVal = defaultValue;
DWORD type = REG_DWORD;
DWORD dwEnabled = 0;
DWORD cb = sizeof(dwEnabled);
if (SHGetValue(HKEY_CURRENT_USER, c_rootRegPath, valueName, &type, &dwEnabled, &cb) == ERROR_SUCCESS)
{
retVal = dwEnabled;
}
return retVal;
}
bool CSettings::SetRegStringValue(_In_ PCWSTR valueName, _In_ PCWSTR value)
{
ULONG cb = (DWORD)((wcslen(value) + 1) * sizeof(*value));
return (SUCCEEDED(HRESULT_FROM_WIN32(SHSetValue(HKEY_CURRENT_USER, c_rootRegPath, valueName, REG_SZ, (const BYTE*)value, cb))));
}
bool CSettings::GetRegStringValue(_In_ PCWSTR valueName, __out_ecount(cchBuf) PWSTR value, DWORD cchBuf)
{
if (cchBuf > 0)
{
value[0] = L'\0';
}
DWORD type = REG_SZ;
ULONG cb = cchBuf * sizeof(*value);
return (SUCCEEDED(HRESULT_FROM_WIN32(SHGetValue(HKEY_CURRENT_USER, c_rootRegPath, valueName, &type, value, &cb) == ERROR_SUCCESS)));
}

View File

@@ -0,0 +1,16 @@
#pragma once
class CSettings
{
public:
static bool GetEnabled();
static bool SetEnabled(_In_ bool enabled);
private:
static bool GetRegBoolValue(_In_ PCWSTR valueName, _In_ bool defaultValue);
static bool SetRegBoolValue(_In_ PCWSTR valueName, _In_ bool value);
static bool SetRegDWORDValue(_In_ PCWSTR valueName, _In_ DWORD value);
static DWORD GetRegDWORDValue(_In_ PCWSTR valueName, _In_ DWORD defaultValue);
static bool SetRegStringValue(_In_ PCWSTR valueName, _In_ PCWSTR value);
static bool GetRegStringValue(_In_ PCWSTR valueName, __out_ecount(cchBuf) PWSTR value, DWORD cchBuf);
};

View File

@@ -0,0 +1,121 @@
#include "stdafx.h"
#include "resource.h"
#include "ImageResizerExt_i.h"
#include "dllmain.h"
#include <interface/powertoy_module_interface.h>
#include <common/settings_objects.h>
#include "Settings.h"
#include "trace.h"
#include <common/common.h>
CImageResizerExtModule _AtlModule;
HINSTANCE g_hInst_imageResizer = 0;
extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
switch (dwReason)
{
case DLL_PROCESS_ATTACH:
g_hInst_imageResizer = hInstance;
Trace::RegisterProvider();
break;
case DLL_PROCESS_DETACH:
Trace::UnregisterProvider();
break;
}
return _AtlModule.DllMain(dwReason, lpReserved);
}
class ImageResizerModule : public PowertoyModuleIface
{
private:
// Enabled by default
bool m_enabled = true;
std::wstring app_name;
public:
// Constructor
ImageResizerModule()
{
m_enabled = CSettings::GetEnabled();
app_name = GET_RESOURCE_STRING(IDS_IMAGERESIZER);
};
// Destroy the powertoy and free memory
virtual void destroy() override
{
delete this;
}
// Return the display name of the powertoy, this will be cached by the runner
virtual const wchar_t* get_name() override
{
return app_name.c_str();
}
// Return array of the names of all events that this powertoy listens for, with
// nullptr as the last element of the array. Nullptr can also be retured for empty
// list.
virtual const wchar_t** get_events() override
{
static const wchar_t* events[] = { nullptr };
return events;
}
// Return JSON with the configuration options.
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
{
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
// Create a Settings object.
PowerToysSettings::Settings settings(hinstance, get_name());
settings.set_description(GET_RESOURCE_STRING(IDS_SETTINGS_DESCRIPTION));
settings.set_overview_link(L"https://github.com/microsoft/PowerToys/blob/master/src/modules/imageresizer/README.md");
settings.set_icon_key(L"pt-image-resizer");
settings.add_header_szLarge(L"imageresizer_settingsheader", GET_RESOURCE_STRING(IDS_SETTINGS_HEADER_DESCRIPTION), GET_RESOURCE_STRING(IDS_SETTINGS_HEADER));
return settings.serialize_to_buffer(buffer, buffer_size);
}
// Signal from the Settings editor to call a custom action.
// This can be used to spawn more complex editors.
virtual void call_custom_action(const wchar_t* action) override {}
// Called by the runner to pass the updated settings values as a serialized JSON.
virtual void set_config(const wchar_t* config) override {}
// Enable the powertoy
virtual void enable()
{
m_enabled = true;
CSettings::SetEnabled(m_enabled);
Trace::EnableImageResizer(m_enabled);
}
// Disable the powertoy
virtual void disable()
{
m_enabled = false;
CSettings::SetEnabled(m_enabled);
Trace::EnableImageResizer(m_enabled);
}
// Returns if the powertoys is enabled
virtual bool is_enabled() override
{
return m_enabled;
}
// Handle incoming event, data is event-specific
virtual intptr_t signal_event(const wchar_t* name, intptr_t data) override
{
return 0;
}
virtual void register_system_menu_helper(PowertoySystemMenuIface* helper) override {}
virtual void signal_system_menu_action(const wchar_t* name) override {}
};
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
{
return new ImageResizerModule();
}

View File

@@ -0,0 +1,8 @@
class CImageResizerExtModule : public ATL::CAtlDllModuleT<CImageResizerExtModule>
{
public:
DECLARE_LIBID(LIBID_ImageResizerExtLib)
DECLARE_REGISTRY_APPID_RESOURCEID(IDR_IMAGERESIZEREXT, "{0C866E7B-65CB-4E7D-B1DD-D014F000E8D8}")
};
extern class CImageResizerExtModule _AtlModule;

View File

@@ -0,0 +1,24 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by ImageResizerExt.rc
//
#define IDS_RESIZE_PICTURES 100
#define IDS_RESIZE_PICTURES_HERE 101
#define IDR_IMAGERESIZEREXT 102
#define IDR_CONTEXTMENUHANDLER 104
#define IDI_RESIZE_PICTURES 105
#define IDS_IMAGERESIZER 106
#define IDS_SETTINGS_DESCRIPTION 107
#define IDS_SETTINGS_HEADER 108
#define IDS_SETTINGS_HEADER_DESCRIPTION 109
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 201
#define _APS_NEXT_COMMAND_VALUE 32768
#define _APS_NEXT_CONTROL_VALUE 201
#define _APS_NEXT_SYMED_VALUE 103
#endif
#endif

View File

@@ -0,0 +1,2 @@
#include "stdafx.h"
#pragma comment(lib, "windowsapp")

View File

@@ -0,0 +1,23 @@
#pragma once
#ifndef STRICT
#define STRICT
#endif
#define _ATL_APARTMENT_THREADED
#define _ATL_NO_AUTOMATIC_NAMESPACE
#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS
#define ATL_NO_ASSERT_ON_DESTROY_NONEXISTENT_WINDOW
#include "targetver.h"
#include "resource.h"
#include <winrt/base.h>
#include <atlbase.h>
#include <atlcom.h>
#include <atlfile.h>
#include <atlstr.h>
#include <windows.h>
#include <ShlObj.h>
#include <ProjectTelemetry.h>

View File

@@ -0,0 +1,4 @@
#pragma once
#include <winsdkver.h>
#include <SDKDDKVer.h>

View File

@@ -0,0 +1,59 @@
#include "stdafx.h"
#include "trace.h"
TRACELOGGING_DEFINE_PROVIDER(
g_hProvider,
"Microsoft.PowerToys",
// {38e8889b-9731-53f5-e901-e8a7c1753074}
(0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74),
TraceLoggingOptionProjectTelemetry());
void Trace::RegisterProvider() noexcept
{
TraceLoggingRegister(g_hProvider);
}
void Trace::UnregisterProvider() noexcept
{
TraceLoggingUnregister(g_hProvider);
}
void Trace::EnableImageResizer(_In_ bool enabled) noexcept
{
TraceLoggingWrite(
g_hProvider,
"ImageResizer_EnableImageResizer",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingBoolean(enabled, "Enabled"));
}
void Trace::Invoked() noexcept
{
TraceLoggingWrite(
g_hProvider,
"ImageResizer_Invoked",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}
void Trace::InvokedRet(_In_ HRESULT hr) noexcept
{
TraceLoggingWrite(
g_hProvider,
"ImageResizer_InvokedRet",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingHResult(hr),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}
void Trace::QueryContextMenuError(_In_ HRESULT hr) noexcept
{
TraceLoggingWrite(
g_hProvider,
"ImageResizer_QueryContextMenuError",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingHResult(hr),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}

View File

@@ -0,0 +1,12 @@
#pragma once
class Trace
{
public:
static void RegisterProvider() noexcept;
static void UnregisterProvider() noexcept;
static void EnableImageResizer(_In_ bool enabled) noexcept;
static void Invoked() noexcept;
static void InvokedRet(_In_ HRESULT hr) noexcept;
static void QueryContextMenuError(_In_ HRESULT hr) noexcept;
};

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/>
</startup>
</configuration>

View File

@@ -0,0 +1,116 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x64</Platform>
<ProjectGuid>{E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ImageResizer</RootNamespace>
<AssemblyName>ImageResizer.Test</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile>
</TargetFrameworkProfile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<IntermediateOutputPath>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(AssemblyName)\</IntermediateOutputPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\$(AssemblyName)</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
<CodeAnalysisRuleSet>..\..\..\codeAnalysis\Rules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\$(AssemblyName)</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
<CodeAnalysisRuleSet>..\..\..\codeAnalysis\Rules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="PresentationCore" />
<Reference Include="System" />
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ui\ImageResizerUI.csproj">
<Project>{2be46397-4dfa-414c-9bd4-41e4bbf8cb34}</Project>
<Name>ImageResizerUI</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="..\..\..\codeAnalysis\StyleCop.json">
<Link>StyleCop.json</Link>
</AdditionalFiles>
<None Include="App.config">
<SubType>Designer</SubType>
</None>
</ItemGroup>
<ItemGroup>
<Compile Include="..\codeAnalysis\GlobalSuppressions.cs">
<Link>GlobalSuppressions.cs</Link>
</Compile>
<Compile Include="Models\CustomSizeTests.cs" />
<Compile Include="Models\ResizeSizeTests.cs" />
<Compile Include="Models\ResizeBatchTests.cs" />
<Compile Include="Models\ResizeOperationTests.cs" />
<Compile Include="Properties\AppFixture.cs" />
<Compile Include="Properties\SettingsTests.cs" />
<Compile Include="Test\AssertEx.cs" />
<Compile Include="Test\BitmapSourceExtensions.cs" />
<Compile Include="Test\TestDirectory.cs" />
<Compile Include="Views\TimeRemainingConverterTests.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="Test.gif">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Test.ico">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Test.jpg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Test.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="TestPortrait.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Moq">
<Version>4.13.1</Version>
</PackageReference>
<PackageReference Include="StyleCop.Analyzers">
<Version>1.1.118</Version>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="System.ValueTuple">
<Version>4.5.0</Version>
</PackageReference>
<PackageReference Include="xunit">
<Version>2.4.1</Version>
</PackageReference>
<PackageReference Include="xunit.runner.visualstudio">
<Version>2.4.1</Version>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@@ -0,0 +1,22 @@
// Copyright (c) Brice Lambson
// The Brice Lambson licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/
using ImageResizer.Properties;
using Xunit;
namespace ImageResizer.Models
{
public class CustomSizeTests
{
[Fact]
public void Name_works()
{
var size = new CustomSize();
size.Name = "Ignored";
Assert.Equal(Resources.Input_Custom, size.Name);
}
}
}

View File

@@ -0,0 +1,108 @@
// Copyright (c) Brice Lambson
// The Brice Lambson licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using Moq;
using Moq.Protected;
using Xunit;
namespace ImageResizer.Models
{
public class ResizeBatchTests
{
private static readonly string EOL = Environment.NewLine;
[Fact]
public void FromCommandLine_works()
{
var standardInput =
"Image1.jpg" + EOL +
"Image2.jpg";
var args = new[]
{
"/d", "OutputDir",
"Image3.jpg",
};
var result = ResizeBatch.FromCommandLine(
new StringReader(standardInput),
args);
Assert.Equal(new List<string> { "Image1.jpg", "Image2.jpg", "Image3.jpg" }, result.Files);
Assert.Equal("OutputDir", result.DestinationDirectory);
}
[Fact]
public void Process_executes_in_parallel()
{
var batch = CreateBatch(_ => Thread.Sleep(50));
batch.Files.AddRange(
Enumerable.Range(0, Environment.ProcessorCount)
.Select(i => "Image" + i + ".jpg"));
var stopwatch = Stopwatch.StartNew();
batch.Process(CancellationToken.None, (_, __) => { });
stopwatch.Stop();
Assert.InRange(stopwatch.ElapsedMilliseconds, 50, 99);
}
[Fact]
public void Process_aggregates_errors()
{
var batch = CreateBatch(file => throw new Exception("Error: " + file));
batch.Files.Add("Image1.jpg");
batch.Files.Add("Image2.jpg");
var errors = batch.Process(CancellationToken.None, (_, __) => { }).ToList();
Assert.Equal(2, errors.Count);
var errorFiles = new List<string>();
foreach (var error in errors)
{
errorFiles.Add(error.File);
Assert.Equal("Error: " + error.File, error.Error);
}
foreach (var file in batch.Files)
{
Assert.Contains(file, errorFiles);
}
}
[Fact]
public void Process_reports_progress()
{
var batch = CreateBatch(_ => { });
batch.Files.Add("Image1.jpg");
batch.Files.Add("Image2.jpg");
var calls = new ConcurrentBag<(int i, double count)>();
batch.Process(
CancellationToken.None,
(i, count) => calls.Add((i, count)));
Assert.Equal(2, calls.Count);
Assert.Contains(calls, c => c.i == 1 && c.count == 2);
Assert.Contains(calls, c => c.i == 2 && c.count == 2);
}
private static ResizeBatch CreateBatch(Action<string> executeAction)
{
var mock = new Mock<ResizeBatch> { CallBase = true };
mock.Protected().Setup("Execute", ItExpr.IsAny<string>()).Callback(executeAction);
return mock.Object;
}
}
}

View File

@@ -0,0 +1,453 @@
// Copyright (c) Brice Lambson
// The Brice Lambson licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using ImageResizer.Properties;
using ImageResizer.Test;
using Xunit;
namespace ImageResizer.Models
{
public class ResizeOperationTests : IDisposable
{
private readonly TestDirectory _directory = new TestDirectory();
[Fact]
public void Execute_copies_frame_metadata()
{
var operation = new ResizeOperation("Test.jpg", _directory, Settings());
operation.Execute();
AssertEx.Image(
_directory.File(),
image => Assert.Equal("Test", ((BitmapMetadata)image.Frames[0].Metadata).Comment));
}
[Fact]
public void Execute_keeps_date_modified()
{
var operation = new ResizeOperation("Test.png", _directory, Settings(s => s.KeepDateModified = true));
operation.Execute();
Assert.Equal(File.GetLastWriteTimeUtc("Test.png"), File.GetLastWriteTimeUtc(_directory.File()));
}
[Fact]
public void Execute_keeps_date_modified_when_replacing_originals()
{
var path = Path.Combine(_directory, "Test.png");
File.Copy("Test.png", path);
var originalDateModified = File.GetLastWriteTimeUtc(path);
var operation = new ResizeOperation(
path,
null,
Settings(
s =>
{
s.KeepDateModified = true;
s.Replace = true;
}));
operation.Execute();
Assert.Equal(originalDateModified, File.GetLastWriteTimeUtc(_directory.File()));
}
[Fact]
public void Execute_replaces_originals()
{
var path = Path.Combine(_directory, "Test.png");
File.Copy("Test.png", path);
var operation = new ResizeOperation(path, null, Settings(s => s.Replace = true));
operation.Execute();
AssertEx.Image(_directory.File(), image => Assert.Equal(96, image.Frames[0].PixelWidth));
}
[Fact]
public void Execute_transforms_each_frame()
{
var operation = new ResizeOperation("Test.gif", _directory, Settings());
operation.Execute();
AssertEx.Image(
_directory.File(),
image =>
{
Assert.Equal(2, image.Frames.Count);
AssertEx.All(image.Frames, frame => Assert.Equal(96, frame.PixelWidth));
});
}
[Fact]
public void Execute_uses_fallback_encoder()
{
var operation = new ResizeOperation(
"Test.ico",
_directory,
Settings(s => s.FallbackEncoder = new PngBitmapEncoder().CodecInfo.ContainerFormat));
operation.Execute();
Assert.Contains("Test (Test).png", _directory.FileNames);
}
[Fact]
public void Transform_ignores_orientation_when_landscape_to_portrait()
{
var operation = new ResizeOperation(
"Test.png",
_directory,
Settings(
x =>
{
x.IgnoreOrientation = true;
x.SelectedSize.Width = 96;
x.SelectedSize.Height = 192;
}));
operation.Execute();
AssertEx.Image(
_directory.File(),
image =>
{
Assert.Equal(192, image.Frames[0].PixelWidth);
Assert.Equal(96, image.Frames[0].PixelHeight);
});
}
[Fact]
public void Transform_ignores_orientation_when_portrait_to_landscape()
{
var operation = new ResizeOperation(
"TestPortrait.png",
_directory,
Settings(
x =>
{
x.IgnoreOrientation = true;
x.SelectedSize.Width = 192;
x.SelectedSize.Height = 96;
}));
operation.Execute();
AssertEx.Image(
_directory.File(),
image =>
{
Assert.Equal(96, image.Frames[0].PixelWidth);
Assert.Equal(192, image.Frames[0].PixelHeight);
});
}
[Fact]
public void Transform_ignores_ignore_orientation_when_auto()
{
var operation = new ResizeOperation(
"Test.png",
_directory,
Settings(
x =>
{
x.IgnoreOrientation = true;
x.SelectedSize.Width = 96;
x.SelectedSize.Height = 0;
}));
operation.Execute();
AssertEx.Image(
_directory.File(),
image =>
{
Assert.Equal(96, image.Frames[0].PixelWidth);
Assert.Equal(48, image.Frames[0].PixelHeight);
});
}
[Fact]
public void Transform_ignores_ignore_orientation_when_percent()
{
var operation = new ResizeOperation(
"Test.png",
_directory,
Settings(
x =>
{
x.IgnoreOrientation = true;
x.SelectedSize.Width = 50;
x.SelectedSize.Height = 200;
x.SelectedSize.Unit = ResizeUnit.Percent;
x.SelectedSize.Fit = ResizeFit.Stretch;
}));
operation.Execute();
AssertEx.Image(
_directory.File(),
image =>
{
Assert.Equal(96, image.Frames[0].PixelWidth);
Assert.Equal(192, image.Frames[0].PixelHeight);
});
}
[Fact]
public void Transform_honors_shrink_only()
{
var operation = new ResizeOperation(
"Test.png",
_directory,
Settings(
x =>
{
x.ShrinkOnly = true;
x.SelectedSize.Width = 288;
x.SelectedSize.Height = 288;
}));
operation.Execute();
AssertEx.Image(
_directory.File(),
image =>
{
Assert.Equal(192, image.Frames[0].PixelWidth);
Assert.Equal(96, image.Frames[0].PixelHeight);
});
}
[Fact]
public void Transform_ignores_shrink_only_when_percent()
{
var operation = new ResizeOperation(
"Test.png",
_directory,
Settings(
x =>
{
x.ShrinkOnly = true;
x.SelectedSize.Width = 133.3;
x.SelectedSize.Unit = ResizeUnit.Percent;
}));
operation.Execute();
AssertEx.Image(
_directory.File(),
image =>
{
Assert.Equal(256, image.Frames[0].PixelWidth);
Assert.Equal(128, image.Frames[0].PixelHeight);
});
}
[Fact]
public void Transform_honors_shrink_only_when_auto_height()
{
var operation = new ResizeOperation(
"Test.png",
_directory,
Settings(
x =>
{
x.ShrinkOnly = true;
x.SelectedSize.Width = 288;
x.SelectedSize.Height = 0;
}));
operation.Execute();
AssertEx.Image(
_directory.File(),
image => Assert.Equal(192, image.Frames[0].PixelWidth));
}
[Fact]
public void Transform_honors_shrink_only_when_auto_width()
{
var operation = new ResizeOperation(
"Test.png",
_directory,
Settings(
x =>
{
x.ShrinkOnly = true;
x.SelectedSize.Width = 0;
x.SelectedSize.Height = 288;
}));
operation.Execute();
AssertEx.Image(
_directory.File(),
image => Assert.Equal(96, image.Frames[0].PixelHeight));
}
[Fact]
public void Transform_honors_unit()
{
var operation = new ResizeOperation(
"Test.png",
_directory,
Settings(
x =>
{
x.SelectedSize.Width = 1;
x.SelectedSize.Height = 1;
x.SelectedSize.Unit = ResizeUnit.Inch;
}));
operation.Execute();
AssertEx.Image(_directory.File(), image => Assert.Equal(image.Frames[0].DpiX, image.Frames[0].PixelWidth, 0));
}
[Fact]
public void Transform_honors_fit_when_Fit()
{
var operation = new ResizeOperation(
"Test.png",
_directory,
Settings(x => x.SelectedSize.Fit = ResizeFit.Fit));
operation.Execute();
AssertEx.Image(
_directory.File(),
image =>
{
Assert.Equal(96, image.Frames[0].PixelWidth);
Assert.Equal(48, image.Frames[0].PixelHeight);
});
}
[Fact]
public void Transform_honors_fit_when_Fill()
{
var operation = new ResizeOperation(
"Test.png",
_directory,
Settings(x => x.SelectedSize.Fit = ResizeFit.Fill));
operation.Execute();
AssertEx.Image(
_directory.File(),
image =>
{
Assert.Equal(Colors.White, image.Frames[0].GetFirstPixel());
Assert.Equal(96, image.Frames[0].PixelWidth);
Assert.Equal(96, image.Frames[0].PixelHeight);
});
}
[Fact]
public void Transform_honors_fit_when_Stretch()
{
var operation = new ResizeOperation(
"Test.png",
_directory,
Settings(x => x.SelectedSize.Fit = ResizeFit.Stretch));
operation.Execute();
AssertEx.Image(
_directory.File(),
image =>
{
Assert.Equal(Colors.Black, image.Frames[0].GetFirstPixel());
Assert.Equal(96, image.Frames[0].PixelWidth);
Assert.Equal(96, image.Frames[0].PixelHeight);
});
}
[Fact]
public void GetDestinationPath_uniquifies_output_filename()
{
File.WriteAllBytes(Path.Combine(_directory, "Test (Test).png"), new byte[0]);
var operation = new ResizeOperation("Test.png", _directory, Settings());
operation.Execute();
Assert.Contains("Test (Test) (1).png", _directory.FileNames);
}
[Fact]
public void GetDestinationPath_uniquifies_output_filename_again()
{
File.WriteAllBytes(Path.Combine(_directory, "Test (Test).png"), new byte[0]);
File.WriteAllBytes(Path.Combine(_directory, "Test (Test) (1).png"), new byte[0]);
var operation = new ResizeOperation("Test.png", _directory, Settings());
operation.Execute();
Assert.Contains("Test (Test) (2).png", _directory.FileNames);
}
[Fact]
public void GetDestinationPath_uses_fileName_format()
{
var operation = new ResizeOperation(
"Test.png",
_directory,
Settings(s => s.FileName = "%1_%2_%3_%4_%5_%6"));
operation.Execute();
Assert.Contains("Test_Test_96_96_96_48.png", _directory.FileNames);
}
[Fact]
public void Execute_handles_directories_in_fileName_format()
{
var operation = new ResizeOperation(
"Test.png",
_directory,
Settings(s => s.FileName = @"Directory\%1 (%2)"));
operation.Execute();
Assert.True(File.Exists(_directory + @"\Directory\Test (Test).png"));
}
public void Dispose()
=> _directory.Dispose();
private Settings Settings(Action<Settings> action = null)
{
var settings = new Settings
{
Sizes = new ObservableCollection<ResizeSize>
{
new ResizeSize
{
Name = "Test",
Width = 96,
Height = 96,
},
},
SelectedSizeIndex = 0,
};
action?.Invoke(settings);
return settings;
}
}
}

View File

@@ -0,0 +1,272 @@
// Copyright (c) Brice Lambson
// The Brice Lambson licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/
using System.Collections.Generic;
using System.ComponentModel;
using ImageResizer.Properties;
using ImageResizer.Test;
using Xunit;
using Xunit.Extensions;
namespace ImageResizer.Models
{
public class ResizeSizeTests
{
[Fact]
public void Name_works()
{
var size = new ResizeSize();
var e = AssertEx.Raises<PropertyChangedEventArgs>(
h => size.PropertyChanged += h,
h => size.PropertyChanged -= h,
() => size.Name = "Test");
Assert.Equal("Test", size.Name);
Assert.Equal(nameof(ResizeSize.Name), e.Arguments.PropertyName);
}
[Fact]
public void Name_replaces_tokens()
{
var args = new List<(string, string)>
{
("$small$", Resources.Small),
("$medium$", Resources.Medium),
("$large$", Resources.Large),
("$phone$", Resources.Phone),
};
foreach (var (name, expected) in args)
{
var size = new ResizeSize();
size.Name = name;
Assert.Equal(expected, size.Name);
}
}
[Fact]
public void Fit_works()
{
var size = new ResizeSize();
var e = AssertEx.Raises<PropertyChangedEventArgs>(
h => size.PropertyChanged += h,
h => size.PropertyChanged -= h,
() => size.Fit = ResizeFit.Stretch);
Assert.Equal(ResizeFit.Stretch, size.Fit);
Assert.Equal(nameof(ResizeSize.Fit), e.Arguments.PropertyName);
}
[Fact]
public void Width_works()
{
var size = new ResizeSize();
var e = AssertEx.Raises<PropertyChangedEventArgs>(
h => size.PropertyChanged += h,
h => size.PropertyChanged -= h,
() => size.Width = 42);
Assert.Equal(42, size.Width);
Assert.Equal(nameof(ResizeSize.Width), e.Arguments.PropertyName);
}
[Fact]
public void Height_works()
{
var size = new ResizeSize();
var e = AssertEx.Raises<PropertyChangedEventArgs>(
h => size.PropertyChanged += h,
h => size.PropertyChanged -= h,
() => size.Height = 42);
Assert.Equal(42, size.Height);
Assert.Equal(nameof(ResizeSize.Height), e.Arguments.PropertyName);
}
[Fact]
public void HasAuto_returns_true_when_Width_unset()
{
var size = new ResizeSize
{
Width = 0,
Height = 42,
};
Assert.True(size.HasAuto);
}
[Fact]
public void HasAuto_returns_true_when_Height_unset()
{
var size = new ResizeSize
{
Width = 42,
Height = 0,
};
Assert.True(size.HasAuto);
}
[Fact]
public void HasAuto_returns_false_when_Width_and_Height_set()
{
var size = new ResizeSize
{
Width = 42,
Height = 42,
};
Assert.False(size.HasAuto);
}
[Fact]
public void Unit_works()
{
var size = new ResizeSize();
var e = AssertEx.Raises<PropertyChangedEventArgs>(
h => size.PropertyChanged += h,
h => size.PropertyChanged -= h,
() => size.Unit = ResizeUnit.Inch);
Assert.Equal(ResizeUnit.Inch, size.Unit);
Assert.Equal(nameof(ResizeSize.Unit), e.Arguments.PropertyName);
}
[Fact]
public void GetPixelWidth_works()
{
var size = new ResizeSize
{
Width = 1,
Unit = ResizeUnit.Inch,
};
var result = size.GetPixelWidth(100, 96);
Assert.Equal(96, result);
}
[Fact]
public void GetPixelHeight_works()
{
var size = new ResizeSize
{
Height = 1,
Unit = ResizeUnit.Inch,
};
var result = size.GetPixelHeight(100, 96);
Assert.Equal(96, result);
}
[Theory]
[InlineData(ResizeFit.Fit)]
[InlineData(ResizeFit.Fill)]
public void GetPixelHeight_uses_Width_when_scale_by_percent(ResizeFit fit)
{
var size = new ResizeSize
{
Fit = fit,
Width = 100,
Height = 50,
Unit = ResizeUnit.Percent,
};
var result = size.GetPixelHeight(100, 96);
Assert.Equal(100, result);
}
[Fact]
public void ConvertToPixels_works_when_auto_and_fit()
{
var size = new ResizeSize
{
Width = 0,
Fit = ResizeFit.Fit,
};
var result = size.GetPixelWidth(100, 96);
Assert.Equal(double.PositiveInfinity, result);
}
[Fact]
public void ConvertToPixels_works_when_auto_and_not_fit()
{
var size = new ResizeSize
{
Width = 0,
Fit = ResizeFit.Fill,
};
var result = size.GetPixelWidth(100, 96);
Assert.Equal(100, result);
}
[Fact]
public void ConvertToPixels_works_when_inches()
{
var size = new ResizeSize
{
Width = 0.5,
Unit = ResizeUnit.Inch,
};
var result = size.GetPixelWidth(100, 96);
Assert.Equal(48, result);
}
[Fact]
public void ConvertToPixels_works_when_centimeters()
{
var size = new ResizeSize
{
Width = 1,
Unit = ResizeUnit.Centimeter,
};
var result = size.GetPixelWidth(100, 96);
Assert.Equal(38, result, 0);
}
[Fact]
public void ConvertToPixels_works_when_percent()
{
var size = new ResizeSize
{
Width = 50,
Unit = ResizeUnit.Percent,
};
var result = size.GetPixelWidth(200, 96);
Assert.Equal(100, result);
}
[Fact]
public void ConvertToPixels_works_when_pixels()
{
var size = new ResizeSize
{
Width = 50,
Unit = ResizeUnit.Pixel,
};
var result = size.GetPixelWidth(100, 96);
Assert.Equal(50, result);
}
}
}

View File

@@ -0,0 +1,26 @@
// 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;
[module: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1636:FileHeaderCopyrightTextMustMatch", Justification = "File created under PowerToys.")]
namespace ImageResizer.Properties
{
public class AppFixture : IDisposable
{
public AppFixture()
{
// new App() needs to be created since Settings.Reload() uses App.Current to update properties on the UI thread. App() can be created only once otherwise it results in System.InvalidOperationException : Cannot create more than one System.Windows.Application instance in the same AppDomain.
imageResizerApp = new App();
}
public void Dispose()
{
imageResizerApp = null;
}
private App imageResizerApp;
}
}

View File

@@ -0,0 +1,280 @@
// Copyright (c) Brice Lambson
// The Brice Lambson licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/
using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using ImageResizer.Models;
using ImageResizer.Test;
using Xunit;
using Xunit.Abstractions;
using Xunit.Extensions;
namespace ImageResizer.Properties
{
public class SettingsTests : IClassFixture<AppFixture>, IDisposable
{
public SettingsTests()
{
// Change settings.json path to a temp file
Settings.SettingsPath = ".\\test_settings.json";
}
public void Dispose()
{
if (System.IO.File.Exists(Settings.SettingsPath))
{
System.IO.File.Delete(Settings.SettingsPath);
}
}
[Fact]
public void AllSizes_propagates_Sizes_collection_events()
{
var settings = new Settings
{
Sizes = new ObservableCollection<ResizeSize>(),
CustomSize = new CustomSize(),
};
var ncc = (INotifyCollectionChanged)settings.AllSizes;
var result = AssertEx.Raises<NotifyCollectionChangedEventArgs>(
h => ncc.CollectionChanged += h,
h => ncc.CollectionChanged -= h,
() => settings.Sizes.Add(new ResizeSize()));
Assert.Equal(NotifyCollectionChangedAction.Add, result.Arguments.Action);
}
[Fact]
public void AllSizes_propagates_Sizes_property_events()
{
var settings = new Settings
{
Sizes = new ObservableCollection<ResizeSize>(),
CustomSize = new CustomSize(),
};
Assert.PropertyChanged(
(INotifyPropertyChanged)settings.AllSizes,
"Item[]",
() => settings.Sizes.Add(new ResizeSize()));
}
[Fact]
public void AllSizes_contains_Sizes()
{
var settings = new Settings
{
Sizes = new ObservableCollection<ResizeSize> { new ResizeSize() },
CustomSize = new CustomSize(),
};
Assert.Contains(settings.Sizes[0], settings.AllSizes);
}
[Fact]
public void AllSizes_contains_CustomSize()
{
var settings = new Settings
{
Sizes = new ObservableCollection<ResizeSize>(),
CustomSize = new CustomSize(),
};
Assert.Contains(settings.CustomSize, settings.AllSizes);
}
[Fact]
public void AllSizes_handles_property_events_for_CustomSize()
{
var originalCustomSize = new CustomSize();
var settings = new Settings
{
Sizes = new ObservableCollection<ResizeSize>(),
CustomSize = originalCustomSize,
};
var ncc = (INotifyCollectionChanged)settings.AllSizes;
var result = AssertEx.Raises<NotifyCollectionChangedEventArgs>(
h => ncc.CollectionChanged += h,
h => ncc.CollectionChanged -= h,
() => settings.CustomSize = new CustomSize());
Assert.Equal(NotifyCollectionChangedAction.Replace, result.Arguments.Action);
Assert.Equal(1, result.Arguments.NewItems.Count);
Assert.Equal(settings.CustomSize, result.Arguments.NewItems[0]);
Assert.Equal(0, result.Arguments.NewStartingIndex);
Assert.Equal(1, result.Arguments.OldItems.Count);
Assert.Equal(originalCustomSize, result.Arguments.OldItems[0]);
Assert.Equal(0, result.Arguments.OldStartingIndex);
}
[Fact]
public void FileNameFormat_works()
{
var settings = new Settings { FileName = "{T}%1e%2s%3t%4%5%6%7" };
var result = settings.FileNameFormat;
Assert.Equal("{{T}}{0}e{1}s{2}t{3}{4}{5}%7", result);
}
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
public void SelectedSize_returns_CustomSize_when_out_of_range(int index)
{
var settings = new Settings
{
SelectedSizeIndex = index,
Sizes = new ObservableCollection<ResizeSize>(),
CustomSize = new CustomSize(),
};
var result = settings.SelectedSize;
Assert.Same(settings.CustomSize, result);
}
[Fact]
public void SelectedSize_returns_Size_when_in_range()
{
var settings = new Settings
{
SelectedSizeIndex = 0,
Sizes = new ObservableCollection<ResizeSize>
{
new ResizeSize(),
},
};
var result = settings.SelectedSize;
Assert.Same(settings.Sizes[0], result);
}
[Fact]
public void IDataErrorInfo_Error_returns_empty()
{
var settings = new Settings();
var result = ((IDataErrorInfo)settings).Error;
Assert.Empty(result);
}
[Theory]
[InlineData(0)]
[InlineData(101)]
public void IDataErrorInfo_Item_JpegQualityLevel_returns_error_when_out_of_range(int value)
{
var settings = new Settings { JpegQualityLevel = value };
var result = ((IDataErrorInfo)settings)["JpegQualityLevel"];
Assert.Equal(
string.Format(Resources.ValueMustBeBetween, 1, 100),
result);
}
[Theory]
[InlineData(1)]
[InlineData(100)]
public void IDataErrorInfo_Item_JpegQualityLevel_returns_empty_when_in_range(int value)
{
var settings = new Settings { JpegQualityLevel = value };
var result = ((IDataErrorInfo)settings)["JpegQualityLevel"];
Assert.Empty(result);
}
[Fact]
public void IDataErrorInfo_Item_returns_empty_when_not_JpegQualityLevel()
{
var settings = new Settings();
var result = ((IDataErrorInfo)settings)["Unknown"];
Assert.Empty(result);
}
[Fact]
public void Reload_createsFile_when_FileNotFound()
{
// Arrange
var settings = new Settings();
// Assert
Assert.False(System.IO.File.Exists(Settings.SettingsPath));
// Act
settings.Reload();
// Assert
Assert.True(System.IO.File.Exists(Settings.SettingsPath));
}
[Fact]
public void Save_creates_file()
{
// Arrange
var settings = new Settings();
// Assert
Assert.False(System.IO.File.Exists(Settings.SettingsPath));
// Act
settings.Save();
// Assert
Assert.True(System.IO.File.Exists(Settings.SettingsPath));
}
[Fact]
public void Save_json_is_readable_by_Reload()
{
// Arrange
var settings = new Settings();
// Assert
Assert.False(System.IO.File.Exists(Settings.SettingsPath));
// Act
settings.Save();
settings.Reload(); // If the JSON file created by Save() is not readable this function will throw an error
// Assert
Assert.True(System.IO.File.Exists(Settings.SettingsPath));
}
[Fact]
public void Reload_raises_PropertyChanged_()
{
// Arrange
var settings = new Settings();
settings.Save(); // To create the settings file
// Act
var action = new System.Action(settings.Reload);
// Assert
Assert.PropertyChanged(settings, "ShrinkOnly", action);
Assert.PropertyChanged(settings, "Replace", action);
Assert.PropertyChanged(settings, "IgnoreOrientation", action);
Assert.PropertyChanged(settings, "JpegQualityLevel", action);
Assert.PropertyChanged(settings, "PngInterlaceOption", action);
Assert.PropertyChanged(settings, "TiffCompressOption", action);
Assert.PropertyChanged(settings, "FileName", action);
Assert.PropertyChanged(settings, "Sizes", action);
Assert.PropertyChanged(settings, "KeepDateModified", action);
Assert.PropertyChanged(settings, "FallbackEncoder", action);
Assert.PropertyChanged(settings, "CustomSize", action);
Assert.PropertyChanged(settings, "SelectedSizeIndex", action);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 B

View File

@@ -0,0 +1,87 @@
// Copyright (c) Brice Lambson
// The Brice Lambson licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.IO;
using System.Windows.Media.Imaging;
using Xunit;
namespace ImageResizer.Test
{
internal static class AssertEx
{
public static void All<T>(IEnumerable<T> collection, Action<T> action)
{
foreach (var item in collection)
{
action(item);
}
}
public static void Image(string path, Action<BitmapDecoder> action)
{
using (var stream = File.OpenRead(path))
{
var image = BitmapDecoder.Create(
stream,
BitmapCreateOptions.PreservePixelFormat,
BitmapCacheOption.None);
action(image);
}
}
public static RaisedEvent<NotifyCollectionChangedEventArgs> Raises<T>(
Action<NotifyCollectionChangedEventHandler> attach,
Action<NotifyCollectionChangedEventHandler> detach,
Action testCode)
where T : NotifyCollectionChangedEventArgs
{
RaisedEvent<NotifyCollectionChangedEventArgs> raisedEvent = null;
NotifyCollectionChangedEventHandler handler = (sender, e)
=> raisedEvent = new RaisedEvent<NotifyCollectionChangedEventArgs>(sender, e);
attach(handler);
testCode();
detach(handler);
Assert.NotNull(raisedEvent);
return raisedEvent;
}
public static RaisedEvent<PropertyChangedEventArgs> Raises<T>(
Action<PropertyChangedEventHandler> attach,
Action<PropertyChangedEventHandler> detach,
Action testCode)
where T : PropertyChangedEventArgs
{
RaisedEvent<PropertyChangedEventArgs> raisedEvent = null;
PropertyChangedEventHandler handler = (sender, e)
=> raisedEvent = new RaisedEvent<PropertyChangedEventArgs>(sender, e);
attach(handler);
testCode();
detach(handler);
Assert.NotNull(raisedEvent);
return raisedEvent;
}
public class RaisedEvent<TArgs>
{
public RaisedEvent(object sender, TArgs args)
{
Sender = sender;
Arguments = args;
}
public object Sender { get; }
public TArgs Arguments { get; }
}
}
}

View File

@@ -0,0 +1,26 @@
// Copyright (c) Brice Lambson
// The Brice Lambson licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace ImageResizer.Test
{
internal static class BitmapSourceExtensions
{
public static Color GetFirstPixel(this BitmapSource source)
{
var pixel = new byte[4];
new FormatConvertedBitmap(
new CroppedBitmap(source, new Int32Rect(0, 0, 1, 1)),
PixelFormats.Bgra32,
destinationPalette: null,
alphaThreshold: 0)
.CopyPixels(pixel, 4, 0);
return Color.FromArgb(pixel[3], pixel[2], pixel[1], pixel[0]);
}
}
}

View File

@@ -0,0 +1,57 @@
// Copyright (c) Brice Lambson
// The Brice Lambson licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using Xunit;
using IOPath = System.IO.Path;
namespace ImageResizer
{
public class TestDirectory : IDisposable
{
private readonly string _path;
public TestDirectory()
{
_path = IOPath.Combine(
AppDomain.CurrentDomain.BaseDirectory,
IOPath.GetRandomFileName());
Directory.CreateDirectory(_path);
}
private IEnumerable<string> Files
=> Directory.EnumerateFiles(_path);
public IEnumerable<string> FileNames
=> Files.Select(IOPath.GetFileName);
public string File()
=> Assert.Single(Files);
public void Dispose()
{
var stopwatch = Stopwatch.StartNew();
while (stopwatch.ElapsedMilliseconds < 30000)
{
try
{
Directory.Delete(_path, recursive: true);
break;
}
catch
{
Thread.Sleep(150);
}
}
}
public static implicit operator string(TestDirectory directory)
=> directory._path;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 B

View File

@@ -0,0 +1,46 @@
// Copyright (c) Brice Lambson
// The Brice Lambson licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/
using System;
using System.Globalization;
using ImageResizer.Properties;
using Xunit;
using Xunit.Extensions;
namespace ImageResizer.Views
{
public class TimeRemainingConverterTests
{
[Theory]
[InlineData("HourMinute", 1, 1, 0)]
[InlineData("HourMinutes", 1, 2, 0)]
[InlineData("HoursMinute", 2, 1, 0)]
[InlineData("HoursMinutes", 2, 2, 0)]
[InlineData("MinuteSecond", 0, 1, 1)]
[InlineData("MinuteSeconds", 0, 1, 2)]
[InlineData("MinutesSecond", 0, 2, 1)]
[InlineData("MinutesSeconds", 0, 2, 2)]
[InlineData("Second", 0, 0, 1)]
[InlineData("Seconds", 0, 0, 2)]
public void Convert_works(string resource, int hours, int minutes, int seconds)
{
var timeRemaining = new TimeSpan(hours, minutes, seconds);
var converter = new TimeRemainingConverter();
var result = converter.Convert(
timeRemaining,
targetType: null,
parameter: null,
CultureInfo.InvariantCulture);
Assert.Equal(
string.Format(
Resources.ResourceManager.GetString("Progress_TimeRemaining_" + resource),
hours,
minutes,
seconds),
result);
}
}
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/>
</startup>
</configuration>

View File

@@ -0,0 +1,32 @@
<Application x:Class="ImageResizer.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:m="clr-namespace:ImageResizer.Models"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:v="clr-namespace:ImageResizer.Views">
<Application.Resources>
<ObjectDataProvider x:Key="ResizeFitValues"
MethodName="GetValues"
ObjectType="sys:Enum">
<ObjectDataProvider.MethodParameters>
<x:Type Type="{x:Type m:ResizeFit}"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<ObjectDataProvider x:Key="ResizeUnitValues"
MethodName="GetValues"
ObjectType="sys:Enum">
<ObjectDataProvider.MethodParameters>
<x:Type Type="{x:Type m:ResizeUnit}"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<v:EnumValueConverter x:Key="EnumValueConverter"/>
<v:AutoDoubleConverter x:Key="AutoDoubleConverter"/>
<v:BoolValueConverter x:Key="BoolValueConverter"/>
<Style x:Key="MainInstructionTextBlockStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="12pt"/>
<Setter Property="Foreground" Value="#039"/>
</Style>
</Application.Resources>
</Application>

View File

@@ -0,0 +1,45 @@
// Copyright (c) Brice Lambson
// The Brice Lambson licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/
using System;
using System.Text;
using System.Windows;
using GalaSoft.MvvmLight.Threading;
using ImageResizer.Models;
using ImageResizer.Properties;
using ImageResizer.Utilities;
using ImageResizer.ViewModels;
using ImageResizer.Views;
namespace ImageResizer
{
public partial class App : Application
{
static App()
{
Console.InputEncoding = Encoding.Unicode;
DispatcherHelper.Initialize();
}
protected override void OnStartup(StartupEventArgs e)
{
var batch = ResizeBatch.FromCommandLine(Console.In, e.Args);
// TODO: Add command-line parameters that can be used in lieu of the input page (issue #14)
var mainWindow = new MainWindow(new MainViewModel(batch, Settings.Default));
mainWindow.Show();
// Temporary workaround for issue #1273
BecomeForegroundWindow(new System.Windows.Interop.WindowInteropHelper(mainWindow).Handle);
}
private void BecomeForegroundWindow(IntPtr hWnd)
{
Win32Helpers.INPUT input = new Win32Helpers.INPUT { type = Win32Helpers.INPUTTYPE.INPUT_MOUSE, data = { } };
Win32Helpers.INPUT[] inputs = new Win32Helpers.INPUT[] { input };
Win32Helpers.SendInput(1, inputs, Win32Helpers.INPUT.Size);
Win32Helpers.SetForegroundWindow(hWnd);
}
}
}

View File

@@ -0,0 +1,23 @@
// Copyright (c) Brice Lambson
// The Brice Lambson licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/
namespace System.Windows.Media.Imaging
{
internal static class BitmapEncoderExtensions
{
public static bool CanEncode(this BitmapEncoder encoder)
{
try
{
var temp = encoder.CodecInfo;
}
catch (NotSupportedException)
{
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,17 @@
// Copyright (c) Brice Lambson
// The Brice Lambson licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/
namespace System.Collections.Generic
{
internal static class ICollectionExtensions
{
public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> items)
{
foreach (var item in items)
{
collection.Add(item);
}
}
}
}

View File

@@ -0,0 +1,12 @@
// Copyright (c) Brice Lambson
// The Brice Lambson licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/
namespace System
{
internal static class TimeSpanExtensions
{
public static TimeSpan Multiply(this TimeSpan timeSpan, double scalar)
=> new TimeSpan((long)(timeSpan.Ticks * scalar));
}
}

View File

@@ -0,0 +1,238 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="..\..\..\..\installer\Version.props" />
<!-- We don't have GenerateAssemblyInfo task until we use .net core, so we generate it with WriteLinesToFile -->
<PropertyGroup>
<AssemblyTitle>ImageResizer</AssemblyTitle>
<AssemblyCompany>Microsoft Corp.</AssemblyCompany>
<AssemblyCopyright>Copyright (C) 2019 Microsoft Corp.</AssemblyCopyright>
</PropertyGroup>
<ItemGroup>
<AssemblyVersionFiles Include="Generated Files\AssemblyInfo.cs"/>
</ItemGroup>
<Target Name="GenerateAssemblyInfo" BeforeTargets="PrepareForBuild">
<ItemGroup>
<HeaderLines Include="// Copyright (c) Microsoft Corporation" />
<HeaderLines Include="// The Microsoft Corporation licenses this file to you under the MIT license." />
<HeaderLines Include="// See the LICENSE file in the project root for more information." />
<HeaderLines Include="#pragma warning disable SA1516" />
<HeaderLines Include="using System.Reflection%3b" />
<HeaderLines Include="using System.Resources%3b" />
<HeaderLines Include="using System.Runtime.InteropServices%3b" />
<HeaderLines Include="using System.Windows%3b" />
<HeaderLines Include="[assembly: AssemblyTitle(&quot;$(AssemblyTitle)&quot;)]" />
<HeaderLines Include="[assembly: AssemblyDescription(&quot;&quot;)]" />
<HeaderLines Include="[assembly: AssemblyConfiguration(&quot;&quot;)]" />
<HeaderLines Include="[assembly: AssemblyCompany(&quot;$(AssemblyCompany)&quot;)]" />
<HeaderLines Include="[assembly: AssemblyCopyright(&quot;$(AssemblyCopyright)&quot;)]" />
<HeaderLines Include="[assembly: AssemblyProduct(&quot;$(AssemblyTitle)&quot;)]" />
<HeaderLines Include="[assembly: AssemblyTrademark(&quot;&quot;)]" />
<HeaderLines Include="[assembly: AssemblyCulture(&quot;&quot;)]" />
<HeaderLines Include="[assembly: ComVisible(false)]" />
<HeaderLines Include="[assembly: NeutralResourcesLanguage(&quot;en-US&quot;)]" />
<HeaderLines Include="[assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]" />
<HeaderLines Include="[assembly: AssemblyVersion(&quot;$(Version).0&quot;)]" />
<HeaderLines Include="[assembly: AssemblyFileVersion(&quot;$(Version).0&quot;)]" />
</ItemGroup>
<WriteLinesToFile
File="Generated Files\AssemblyInfo.cs"
Lines="@(HeaderLines)"
Overwrite="true"
Encoding="Unicode"
WriteOnlyWhenDifferent="true" />
</Target>
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x64</Platform>
<ProjectGuid>{2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>ImageResizer</RootNamespace>
<AssemblyName>ImageResizer</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<TargetFrameworkProfile>
</TargetFrameworkProfile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<IntermediateOutputPath>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(AssemblyName)\</IntermediateOutputPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
<PlatformTarget>x64</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
<CodeAnalysisRuleSet>..\..\..\codeAnalysis\Rules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
<PlatformTarget>x64</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
<CodeAnalysisRuleSet>..\..\..\codeAnalysis\Rules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>Resources\ImageResizer.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.VisualBasic" />
<Reference Include="System" />
<Reference Include="System.Xaml" />
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="..\codeAnalysis\GlobalSuppressions.cs">
<Link>GlobalSuppressions.cs</Link>
</Compile>
<Compile Include="Extensions\BitmapEncoderExtensions.cs" />
<Compile Include="Extensions\ICollectionExtensions.cs" />
<Compile Include="Extensions\TimeSpanExtensions.cs" />
<Compile Include="Models\ResizeError.cs" />
<Compile Include="Properties\Settings.cs" />
<Compile Include="Models\CustomSize.cs" />
<Compile Include="Properties\InternalsVisibleTo.cs" />
<Compile Include="Models\ResizeBatch.cs" />
<Compile Include="Models\ResizeFit.cs" />
<Compile Include="Models\ResizeOperation.cs" />
<Compile Include="Models\ResizeSize.cs" />
<Compile Include="Models\ResizeUnit.cs" />
<Compile Include="Utilities\MathHelpers.cs" />
<Compile Include="Utilities\Win32Helpers.cs" />
<Compile Include="ViewModels\AdvancedViewModel.cs" />
<Compile Include="ViewModels\InputViewModel.cs" />
<Compile Include="ViewModels\ITabViewModel.cs" />
<Compile Include="Views\AutoDoubleValidationRule.cs" />
<Compile Include="Views\BoolValueConverter.cs" />
<Compile Include="Views\ContainerFormatConverter.cs" />
<Compile Include="Views\AutoDoubleConverter.cs" />
<Compile Include="Views\IMainView.cs" />
<Compile Include="ViewModels\MainViewModel.cs" />
<Compile Include="ViewModels\ProgressViewModel.cs" />
<Compile Include="ViewModels\ResultsViewModel.cs" />
<Compile Include="Views\AdvancedWindow.xaml.cs">
<DependentUpon>AdvancedWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Views\TiffCompressOptionConverter.cs" />
<Compile Include="Views\EnumValueConverter.cs" />
<Compile Include="Views\ProgressPage.xaml.cs">
<DependentUpon>ProgressPage.xaml</DependentUpon>
</Compile>
<Compile Include="Views\ResizeUnitConverter.cs" />
<Compile Include="Views\ResultsPage.xaml.cs">
<DependentUpon>ResultsPage.xaml</DependentUpon>
</Compile>
<Compile Include="Views\TimeRemainingConverter.cs" />
<Page Include="Views\AdvancedWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\InputPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="Views\InputPage.xaml.cs">
<DependentUpon>InputPage.xaml</DependentUpon>
</Compile>
<Compile Include="Views\MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Page Include="Views\ProgressPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\ResultsPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Compile Include="Generated Files\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<EmbeddedResource Include="Properties\Resources.ar.resx" />
<EmbeddedResource Include="Properties\Resources.bg.resx" />
<EmbeddedResource Include="Properties\Resources.ca.resx" />
<EmbeddedResource Include="Properties\Resources.cs.resx" />
<EmbeddedResource Include="Properties\Resources.de.resx" />
<EmbeddedResource Include="Properties\Resources.es.resx" />
<EmbeddedResource Include="Properties\Resources.eu-ES.resx" />
<EmbeddedResource Include="Properties\Resources.fr.resx" />
<EmbeddedResource Include="Properties\Resources.he.resx" />
<EmbeddedResource Include="Properties\Resources.hu.resx" />
<EmbeddedResource Include="Properties\Resources.it.resx" />
<EmbeddedResource Include="Properties\Resources.nb-NO.resx" />
<EmbeddedResource Include="Properties\Resources.nl.resx" />
<EmbeddedResource Include="Properties\Resources.pl.resx" />
<EmbeddedResource Include="Properties\Resources.pt-BR.resx" />
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.ru.resx" />
<EmbeddedResource Include="Properties\Resources.sk.resx" />
<EmbeddedResource Include="Properties\Resources.tr.resx" />
<EmbeddedResource Include="Properties\Resources.zh-Hans.resx" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="..\..\..\codeAnalysis\StyleCop.json" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\ImageResizer.ico" />
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\ImageResizer.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MvvmLightLibs">
<Version>5.4.1.1</Version>
</PackageReference>
<PackageReference Include="Newtonsoft.Json">
<Version>12.0.3</Version>
</PackageReference>
<PackageReference Include="StyleCop.Analyzers">
<Version>1.1.118</Version>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<TargetFrameworkSDKToolsDirectory Condition=" '$(Platform)' == 'x64'">$(TargetFrameworkSDKToolsDirectory)$(Platform)\</TargetFrameworkSDKToolsDirectory>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,31 @@
// Copyright (c) Brice Lambson
// The Brice Lambson licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/
using ImageResizer.Properties;
using Newtonsoft.Json;
namespace ImageResizer.Models
{
public class CustomSize : ResizeSize
{
[JsonIgnore]
public override string Name
{
get => Resources.Input_Custom;
set { /* no-op */ }
}
public CustomSize(ResizeFit fit, double width, double height, ResizeUnit unit)
{
Fit = fit;
Width = width;
Height = height;
Unit = unit;
}
public CustomSize()
{
}
}
}

View File

@@ -0,0 +1,85 @@
// Copyright (c) Brice Lambson
// The Brice Lambson licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using ImageResizer.Properties;
namespace ImageResizer.Models
{
public class ResizeBatch
{
public string DestinationDirectory { get; set; }
public ICollection<string> Files { get; } = new List<string>();
public static ResizeBatch FromCommandLine(TextReader standardInput, string[] args)
{
var batch = new ResizeBatch();
// NB: We read these from stdin since there are limits on the number of args you can have
string file;
while ((file = standardInput.ReadLine()) != null)
{
batch.Files.Add(file);
}
for (var i = 0; i < args.Length; i++)
{
if (args[i] == "/d")
{
batch.DestinationDirectory = args[++i];
continue;
}
batch.Files.Add(args[i]);
}
return batch;
}
public IEnumerable<ResizeError> Process(
CancellationToken cancellationToken,
Action<int, double> reportProgress)
{
double total = Files.Count;
var completed = 0;
var errors = new ConcurrentBag<ResizeError>();
// TODO: If we ever switch to Windows.Graphics.Imaging, we can get a lot more throughput by using the async
// APIs and a custom SynchronizationContext
Parallel.ForEach(
Files,
new ParallelOptions
{
CancellationToken = cancellationToken,
MaxDegreeOfParallelism = Environment.ProcessorCount,
},
(file, state, i) =>
{
try
{
Execute(file);
}
catch (Exception ex)
{
errors.Add(new ResizeError { File = Path.GetFileName(file), Error = ex.Message });
}
Interlocked.Increment(ref completed);
reportProgress(completed, total);
});
return errors;
}
protected virtual void Execute(string file)
=> new ResizeOperation(file, DestinationDirectory, Settings.Default).Execute();
}
}

View File

@@ -0,0 +1,13 @@
// Copyright (c) Brice Lambson
// The Brice Lambson licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/
namespace ImageResizer.Models
{
public class ResizeError
{
public string File { get; set; }
public string Error { get; set; }
}
}

View File

@@ -0,0 +1,13 @@
// Copyright (c) Brice Lambson
// The Brice Lambson licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/
namespace ImageResizer.Models
{
public enum ResizeFit
{
Fill,
Fit,
Stretch,
}
}

View File

@@ -0,0 +1,211 @@
// Copyright (c) Brice Lambson
// The Brice Lambson licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/
using System;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using ImageResizer.Properties;
using ImageResizer.Utilities;
using Microsoft.VisualBasic.FileIO;
namespace ImageResizer.Models
{
internal class ResizeOperation
{
private readonly string _file;
private readonly string _destinationDirectory;
private readonly Settings _settings;
public ResizeOperation(string file, string destinationDirectory, Settings settings)
{
_file = file;
_destinationDirectory = destinationDirectory;
_settings = settings;
}
public void Execute()
{
string path;
using (var inputStream = File.OpenRead(_file))
{
var decoder = BitmapDecoder.Create(
inputStream,
BitmapCreateOptions.PreservePixelFormat,
BitmapCacheOption.None);
var encoder = BitmapEncoder.Create(decoder.CodecInfo.ContainerFormat);
if (!encoder.CanEncode())
{
encoder = BitmapEncoder.Create(_settings.FallbackEncoder);
}
ConfigureEncoder(encoder);
if (decoder.Metadata != null)
{
try
{
encoder.Metadata = decoder.Metadata;
}
catch (InvalidOperationException)
{
}
}
if (decoder.Palette != null)
{
encoder.Palette = decoder.Palette;
}
foreach (var originalFrame in decoder.Frames)
{
encoder.Frames.Add(
BitmapFrame.Create(
Transform(originalFrame),
thumbnail: null,
(BitmapMetadata)originalFrame.Metadata, // TODO: Add an option to strip any metadata that doesn't affect rendering (issue #3)
colorContexts: null));
}
path = GetDestinationPath(encoder);
Directory.CreateDirectory(Path.GetDirectoryName(path));
using (var outputStream = File.Open(path, FileMode.CreateNew, FileAccess.Write))
{
encoder.Save(outputStream);
}
}
if (_settings.KeepDateModified)
{
File.SetLastWriteTimeUtc(path, File.GetLastWriteTimeUtc(_file));
}
if (_settings.Replace)
{
var backup = GetBackupPath();
File.Replace(path, _file, backup, ignoreMetadataErrors: true);
FileSystem.DeleteFile(backup, UIOption.OnlyErrorDialogs, RecycleOption.SendToRecycleBin);
}
}
private void ConfigureEncoder(BitmapEncoder encoder)
{
switch (encoder)
{
case JpegBitmapEncoder jpegEncoder:
jpegEncoder.QualityLevel = MathHelpers.Clamp(_settings.JpegQualityLevel, 1, 100);
break;
case PngBitmapEncoder pngBitmapEncoder:
pngBitmapEncoder.Interlace = _settings.PngInterlaceOption;
break;
case TiffBitmapEncoder tiffEncoder:
tiffEncoder.Compression = _settings.TiffCompressOption;
break;
}
}
private BitmapSource Transform(BitmapSource source)
{
var originalWidth = source.PixelWidth;
var originalHeight = source.PixelHeight;
var width = _settings.SelectedSize.GetPixelWidth(originalWidth, source.DpiX);
var height = _settings.SelectedSize.GetPixelHeight(originalHeight, source.DpiY);
if (_settings.IgnoreOrientation
&& !_settings.SelectedSize.HasAuto
&& _settings.SelectedSize.Unit != ResizeUnit.Percent
&& originalWidth < originalHeight != (width < height))
{
var temp = width;
width = height;
height = temp;
}
var scaleX = width / originalWidth;
var scaleY = height / originalHeight;
if (_settings.SelectedSize.Fit == ResizeFit.Fit)
{
scaleX = Math.Min(scaleX, scaleY);
scaleY = scaleX;
}
else if (_settings.SelectedSize.Fit == ResizeFit.Fill)
{
scaleX = Math.Max(scaleX, scaleY);
scaleY = scaleX;
}
if (_settings.ShrinkOnly
&& _settings.SelectedSize.Unit != ResizeUnit.Percent
&& (scaleX >= 1 || scaleY >= 1))
{
return source;
}
var scaledBitmap = new TransformedBitmap(source, new ScaleTransform(scaleX, scaleY));
if (_settings.SelectedSize.Fit == ResizeFit.Fill
&& (scaledBitmap.PixelWidth > width
|| scaledBitmap.PixelHeight > height))
{
var x = (int)(((originalWidth * scaleX) - width) / 2);
var y = (int)(((originalHeight * scaleY) - height) / 2);
return new CroppedBitmap(scaledBitmap, new Int32Rect(x, y, (int)width, (int)height));
}
return scaledBitmap;
}
private string GetDestinationPath(BitmapEncoder encoder)
{
var directory = _destinationDirectory ?? Path.GetDirectoryName(_file);
var originalFileName = Path.GetFileNameWithoutExtension(_file);
var supportedExtensions = encoder.CodecInfo.FileExtensions.Split(',');
var extension = Path.GetExtension(_file);
if (!supportedExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{
extension = supportedExtensions.FirstOrDefault();
}
var fileName = string.Format(
_settings.FileNameFormat,
originalFileName,
_settings.SelectedSize.Name,
_settings.SelectedSize.Width,
_settings.SelectedSize.Height,
encoder.Frames[0].PixelWidth,
encoder.Frames[0].PixelHeight);
var path = Path.Combine(directory, fileName + extension);
var uniquifier = 1;
while (File.Exists(path))
{
path = Path.Combine(directory, fileName + " (" + uniquifier++ + ")" + extension);
}
return path;
}
private string GetBackupPath()
{
var directory = Path.GetDirectoryName(_file);
var fileName = Path.GetFileNameWithoutExtension(_file);
var extension = Path.GetExtension(_file);
var path = Path.Combine(directory, fileName + ".bak" + extension);
var uniquifier = 1;
while (File.Exists(path))
{
path = Path.Combine(directory, fileName + " (" + uniquifier++ + ")" + ".bak" + extension);
}
return path;
}
}
}

Some files were not shown because too many files have changed in this diff Show More