mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-04 18:26:39 +02:00
Merged powerToys master into Launcher master
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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="" 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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 Ctrl Key repeats"/>-->
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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{};
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
Binary file not shown.
@@ -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
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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>
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
;
|
||||
}
|
||||
|
||||
@@ -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"";
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
|
||||
29
src/modules/imageresizer/README.md
Normal file
29
src/modules/imageresizer/README.md
Normal 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 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.
|
||||
|
||||

|
||||
|
||||
## Settings
|
||||
|
||||

|
||||
|
||||
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.
|
||||
47
src/modules/imageresizer/codeAnalysis/GlobalSuppressions.cs
Normal file
47
src/modules/imageresizer/codeAnalysis/GlobalSuppressions.cs
Normal 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")]
|
||||
407
src/modules/imageresizer/dll/ContextMenuHandler.cpp
Normal file
407
src/modules/imageresizer/dll/ContextMenuHandler.cpp
Normal 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;
|
||||
}
|
||||
57
src/modules/imageresizer/dll/ContextMenuHandler.h
Normal file
57
src/modules/imageresizer/dll/ContextMenuHandler.h
Normal 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)
|
||||
3
src/modules/imageresizer/dll/ContextMenuHandler.rgs
Normal file
3
src/modules/imageresizer/dll/ContextMenuHandler.rgs
Normal file
@@ -0,0 +1,3 @@
|
||||
HKCR
|
||||
{
|
||||
}
|
||||
49
src/modules/imageresizer/dll/HDropIterator.cpp
Normal file
49
src/modules/imageresizer/dll/HDropIterator.cpp
Normal 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;
|
||||
}
|
||||
17
src/modules/imageresizer/dll/HDropIterator.h
Normal file
17
src/modules/imageresizer/dll/HDropIterator.h
Normal 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;
|
||||
};
|
||||
54
src/modules/imageresizer/dll/ImageResizerExt.cpp
Normal file
54
src/modules/imageresizer/dll/ImageResizerExt.cpp
Normal 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;
|
||||
}
|
||||
8
src/modules/imageresizer/dll/ImageResizerExt.def
Normal file
8
src/modules/imageresizer/dll/ImageResizerExt.def
Normal file
@@ -0,0 +1,8 @@
|
||||
LIBRARY
|
||||
|
||||
EXPORTS
|
||||
DllCanUnloadNow PRIVATE
|
||||
DllGetClassObject PRIVATE
|
||||
DllRegisterServer PRIVATE
|
||||
DllUnregisterServer PRIVATE
|
||||
DllInstall PRIVATE
|
||||
15
src/modules/imageresizer/dll/ImageResizerExt.idl
Normal file
15
src/modules/imageresizer/dll/ImageResizerExt.idl
Normal 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;
|
||||
};
|
||||
};
|
||||
BIN
src/modules/imageresizer/dll/ImageResizerExt.rc
Normal file
BIN
src/modules/imageresizer/dll/ImageResizerExt.rc
Normal file
Binary file not shown.
3
src/modules/imageresizer/dll/ImageResizerExt.rgs
Normal file
3
src/modules/imageresizer/dll/ImageResizerExt.rgs
Normal file
@@ -0,0 +1,3 @@
|
||||
HKCR
|
||||
{
|
||||
}
|
||||
313
src/modules/imageresizer/dll/ImageResizerExt.vcxproj
Normal file
313
src/modules/imageresizer/dll/ImageResizerExt.vcxproj
Normal 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>
|
||||
102
src/modules/imageresizer/dll/ImageResizerExt.vcxproj.filters
Normal file
102
src/modules/imageresizer/dll/ImageResizerExt.vcxproj.filters
Normal 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>
|
||||
66
src/modules/imageresizer/dll/Settings.cpp
Normal file
66
src/modules/imageresizer/dll/Settings.cpp
Normal 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)));
|
||||
}
|
||||
16
src/modules/imageresizer/dll/Settings.h
Normal file
16
src/modules/imageresizer/dll/Settings.h
Normal 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);
|
||||
};
|
||||
121
src/modules/imageresizer/dll/dllmain.cpp
Normal file
121
src/modules/imageresizer/dll/dllmain.cpp
Normal 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();
|
||||
}
|
||||
8
src/modules/imageresizer/dll/dllmain.h
Normal file
8
src/modules/imageresizer/dll/dllmain.h
Normal 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;
|
||||
24
src/modules/imageresizer/dll/resource.h
Normal file
24
src/modules/imageresizer/dll/resource.h
Normal 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
|
||||
2
src/modules/imageresizer/dll/stdafx.cpp
Normal file
2
src/modules/imageresizer/dll/stdafx.cpp
Normal file
@@ -0,0 +1,2 @@
|
||||
#include "stdafx.h"
|
||||
#pragma comment(lib, "windowsapp")
|
||||
23
src/modules/imageresizer/dll/stdafx.h
Normal file
23
src/modules/imageresizer/dll/stdafx.h
Normal 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>
|
||||
4
src/modules/imageresizer/dll/targetver.h
Normal file
4
src/modules/imageresizer/dll/targetver.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
#include <winsdkver.h>
|
||||
#include <SDKDDKVer.h>
|
||||
59
src/modules/imageresizer/dll/trace.cpp
Normal file
59
src/modules/imageresizer/dll/trace.cpp
Normal 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));
|
||||
}
|
||||
12
src/modules/imageresizer/dll/trace.h
Normal file
12
src/modules/imageresizer/dll/trace.h
Normal 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;
|
||||
};
|
||||
6
src/modules/imageresizer/tests/App.config
Normal file
6
src/modules/imageresizer/tests/App.config
Normal 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>
|
||||
116
src/modules/imageresizer/tests/ImageResizerUITest.csproj
Normal file
116
src/modules/imageresizer/tests/ImageResizerUITest.csproj
Normal 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>
|
||||
22
src/modules/imageresizer/tests/Models/CustomSizeTests.cs
Normal file
22
src/modules/imageresizer/tests/Models/CustomSizeTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
108
src/modules/imageresizer/tests/Models/ResizeBatchTests.cs
Normal file
108
src/modules/imageresizer/tests/Models/ResizeBatchTests.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
453
src/modules/imageresizer/tests/Models/ResizeOperationTests.cs
Normal file
453
src/modules/imageresizer/tests/Models/ResizeOperationTests.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
272
src/modules/imageresizer/tests/Models/ResizeSizeTests.cs
Normal file
272
src/modules/imageresizer/tests/Models/ResizeSizeTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/modules/imageresizer/tests/Properties/AppFixture.cs
Normal file
26
src/modules/imageresizer/tests/Properties/AppFixture.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
280
src/modules/imageresizer/tests/Properties/SettingsTests.cs
Normal file
280
src/modules/imageresizer/tests/Properties/SettingsTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
src/modules/imageresizer/tests/Test.gif
Normal file
BIN
src/modules/imageresizer/tests/Test.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
BIN
src/modules/imageresizer/tests/Test.ico
Normal file
BIN
src/modules/imageresizer/tests/Test.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
BIN
src/modules/imageresizer/tests/Test.jpg
Normal file
BIN
src/modules/imageresizer/tests/Test.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.6 KiB |
BIN
src/modules/imageresizer/tests/Test.png
Normal file
BIN
src/modules/imageresizer/tests/Test.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 781 B |
87
src/modules/imageresizer/tests/Test/AssertEx.cs
Normal file
87
src/modules/imageresizer/tests/Test/AssertEx.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
57
src/modules/imageresizer/tests/Test/TestDirectory.cs
Normal file
57
src/modules/imageresizer/tests/Test/TestDirectory.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
BIN
src/modules/imageresizer/tests/TestPortrait.png
Normal file
BIN
src/modules/imageresizer/tests/TestPortrait.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 656 B |
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
6
src/modules/imageresizer/ui/App.config
Normal file
6
src/modules/imageresizer/ui/App.config
Normal 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>
|
||||
32
src/modules/imageresizer/ui/App.xaml
Normal file
32
src/modules/imageresizer/ui/App.xaml
Normal 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>
|
||||
45
src/modules/imageresizer/ui/App.xaml.cs
Normal file
45
src/modules/imageresizer/ui/App.xaml.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/modules/imageresizer/ui/Extensions/TimeSpanExtensions.cs
Normal file
12
src/modules/imageresizer/ui/Extensions/TimeSpanExtensions.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
238
src/modules/imageresizer/ui/ImageResizerUI.csproj
Normal file
238
src/modules/imageresizer/ui/ImageResizerUI.csproj
Normal 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("$(AssemblyTitle)")]" />
|
||||
<HeaderLines Include="[assembly: AssemblyDescription("")]" />
|
||||
<HeaderLines Include="[assembly: AssemblyConfiguration("")]" />
|
||||
<HeaderLines Include="[assembly: AssemblyCompany("$(AssemblyCompany)")]" />
|
||||
<HeaderLines Include="[assembly: AssemblyCopyright("$(AssemblyCopyright)")]" />
|
||||
<HeaderLines Include="[assembly: AssemblyProduct("$(AssemblyTitle)")]" />
|
||||
<HeaderLines Include="[assembly: AssemblyTrademark("")]" />
|
||||
<HeaderLines Include="[assembly: AssemblyCulture("")]" />
|
||||
<HeaderLines Include="[assembly: ComVisible(false)]" />
|
||||
<HeaderLines Include="[assembly: NeutralResourcesLanguage("en-US")]" />
|
||||
<HeaderLines Include="[assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]" />
|
||||
<HeaderLines Include="[assembly: AssemblyVersion("$(Version).0")]" />
|
||||
<HeaderLines Include="[assembly: AssemblyFileVersion("$(Version).0")]" />
|
||||
</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>
|
||||
31
src/modules/imageresizer/ui/Models/CustomSize.cs
Normal file
31
src/modules/imageresizer/ui/Models/CustomSize.cs
Normal 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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
85
src/modules/imageresizer/ui/Models/ResizeBatch.cs
Normal file
85
src/modules/imageresizer/ui/Models/ResizeBatch.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
13
src/modules/imageresizer/ui/Models/ResizeError.cs
Normal file
13
src/modules/imageresizer/ui/Models/ResizeError.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
13
src/modules/imageresizer/ui/Models/ResizeFit.cs
Normal file
13
src/modules/imageresizer/ui/Models/ResizeFit.cs
Normal 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,
|
||||
}
|
||||
}
|
||||
211
src/modules/imageresizer/ui/Models/ResizeOperation.cs
Normal file
211
src/modules/imageresizer/ui/Models/ResizeOperation.cs
Normal 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
Reference in New Issue
Block a user