Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects

This commit is contained in:
seraphima
2024-06-05 11:28:45 +02:00
11 changed files with 430 additions and 74 deletions

View File

@@ -21,52 +21,120 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ProjectsLauncher", "Project
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProjectsEditor", "ProjectsEditor\ProjectsEditor.csproj", "{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ManagedCommon", "..\..\common\ManagedCommon\ManagedCommon.csproj", "{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerToys.Interop", "..\..\common\interop\PowerToys.Interop.vcxproj", "{F055103B-F80B-4D0C-BF48-057C55620033}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ManagedTelemetry", "..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj", "{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|ARM64 = Debug|ARM64
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|ARM64 = Release|ARM64
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|Any CPU.ActiveCfg = Debug|x64
{3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|Any CPU.Build.0 = Debug|x64
{3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|ARM64.ActiveCfg = Debug|ARM64
{3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|ARM64.Build.0 = Debug|ARM64
{3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|x64.ActiveCfg = Debug|x64
{3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|x64.Build.0 = Debug|x64
{3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|x86.ActiveCfg = Debug|Win32
{3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|x86.Build.0 = Debug|Win32
{3D63307B-9D27-44FD-B033-B26F39245B85}.Release|Any CPU.ActiveCfg = Release|x64
{3D63307B-9D27-44FD-B033-B26F39245B85}.Release|Any CPU.Build.0 = Release|x64
{3D63307B-9D27-44FD-B033-B26F39245B85}.Release|ARM64.ActiveCfg = Release|ARM64
{3D63307B-9D27-44FD-B033-B26F39245B85}.Release|ARM64.Build.0 = Release|ARM64
{3D63307B-9D27-44FD-B033-B26F39245B85}.Release|x64.ActiveCfg = Release|x64
{3D63307B-9D27-44FD-B033-B26F39245B85}.Release|x64.Build.0 = Release|x64
{3D63307B-9D27-44FD-B033-B26F39245B85}.Release|x86.ActiveCfg = Release|Win32
{3D63307B-9D27-44FD-B033-B26F39245B85}.Release|x86.Build.0 = Release|Win32
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|Any CPU.ActiveCfg = Debug|x64
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|Any CPU.Build.0 = Debug|x64
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|ARM64.ActiveCfg = Debug|ARM64
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|ARM64.Build.0 = Debug|ARM64
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|x64.ActiveCfg = Debug|x64
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|x64.Build.0 = Debug|x64
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|x86.ActiveCfg = Debug|Win32
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|x86.Build.0 = Debug|Win32
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|Any CPU.ActiveCfg = Release|x64
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|Any CPU.Build.0 = Release|x64
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|ARM64.ActiveCfg = Release|ARM64
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|ARM64.Build.0 = Release|ARM64
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x64.ActiveCfg = Release|x64
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x64.Build.0 = Release|x64
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x86.ActiveCfg = Release|Win32
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x86.Build.0 = Release|Win32
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|ARM64.Build.0 = Debug|Any CPU
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|x64.ActiveCfg = Debug|x64
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|x64.Build.0 = Debug|x64
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|x86.ActiveCfg = Debug|Any CPU
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|x86.Build.0 = Debug|Any CPU
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|Any CPU.Build.0 = Release|Any CPU
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|ARM64.ActiveCfg = Release|Any CPU
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|ARM64.Build.0 = Release|Any CPU
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|x64.ActiveCfg = Release|x64
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|x64.Build.0 = Release|x64
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|x86.ActiveCfg = Release|Any CPU
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|x86.Build.0 = Release|Any CPU
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Debug|Any CPU.ActiveCfg = Debug|x64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Debug|Any CPU.Build.0 = Debug|x64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Debug|ARM64.ActiveCfg = Debug|ARM64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Debug|ARM64.Build.0 = Debug|ARM64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Debug|x64.ActiveCfg = Debug|x64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Debug|x64.Build.0 = Debug|x64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Debug|x86.ActiveCfg = Debug|x64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Debug|x86.Build.0 = Debug|x64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Release|Any CPU.ActiveCfg = Release|x64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Release|Any CPU.Build.0 = Release|x64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Release|ARM64.ActiveCfg = Release|ARM64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Release|ARM64.Build.0 = Release|ARM64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Release|x64.ActiveCfg = Release|x64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Release|x64.Build.0 = Release|x64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Release|x86.ActiveCfg = Release|x64
{A881F6EB-6EDA-4674-A6B7-598F0A8E7048}.Release|x86.Build.0 = Release|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Debug|Any CPU.ActiveCfg = Debug|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Debug|Any CPU.Build.0 = Debug|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Debug|ARM64.ActiveCfg = Debug|ARM64
{F055103B-F80B-4D0C-BF48-057C55620033}.Debug|ARM64.Build.0 = Debug|ARM64
{F055103B-F80B-4D0C-BF48-057C55620033}.Debug|x64.ActiveCfg = Debug|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Debug|x64.Build.0 = Debug|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Debug|x86.ActiveCfg = Debug|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Debug|x86.Build.0 = Debug|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|Any CPU.ActiveCfg = Release|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|Any CPU.Build.0 = Release|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|ARM64.ActiveCfg = Release|ARM64
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|ARM64.Build.0 = Release|ARM64
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x64.ActiveCfg = Release|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x64.Build.0 = Release|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x86.ActiveCfg = Release|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Release|x86.Build.0 = Release|x64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Debug|Any CPU.ActiveCfg = Debug|x64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Debug|Any CPU.Build.0 = Debug|x64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Debug|ARM64.ActiveCfg = Debug|ARM64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Debug|ARM64.Build.0 = Debug|ARM64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Debug|x64.ActiveCfg = Debug|x64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Debug|x64.Build.0 = Debug|x64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Debug|x86.ActiveCfg = Debug|x64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Debug|x86.Build.0 = Debug|x64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Release|Any CPU.ActiveCfg = Release|x64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Release|Any CPU.Build.0 = Release|x64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Release|ARM64.ActiveCfg = Release|ARM64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Release|ARM64.Build.0 = Release|ARM64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Release|x64.ActiveCfg = Release|x64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Release|x64.Build.0 = Release|x64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Release|x86.ActiveCfg = Release|x64
{6CE421AD-D249-4BD1-9ADA-B46DA18AADEE}.Release|x86.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -4,6 +4,7 @@
using System;
using System.Windows;
using ManagedCommon;
using ProjectsEditor.Common;
using ProjectsEditor.Utils;
using ProjectsEditor.ViewModels;
@@ -34,6 +35,8 @@ namespace ProjectsEditor
{
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
Logger.InitializeLogger("\\Projects\\Logs");
_themeManager = new ThemeManager(this);
if (_mainViewModel == null)
@@ -46,6 +49,7 @@ namespace ProjectsEditor
string[] args = Environment.GetCommandLineArgs();
if (args != null && args.Length > 1)
{
Logger.LogInfo($"Started with a parameter: {args[1]}. Trying to launch that project.");
_mainViewModel.LaunchProject(args[1]);
return;
}

View File

@@ -12,7 +12,11 @@ using System.IO;
using System.Linq;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;
using ManagedCommon;
using Windows.ApplicationModel.Core;
using Windows.Management.Deployment;
namespace ProjectsEditor.Models
{
@@ -77,10 +81,22 @@ namespace ProjectsEditor.Models
{
try
{
_icon = Icon.ExtractAssociatedIcon(AppPath);
if (!File.Exists(AppPath) && IsPackagedApp)
{
Task<AppListEntry> task = Task.Run<AppListEntry>(async () => await GetAppByPackageFamilyNameAsync());
AppListEntry packApp = task.Result;
string filename = Path.GetFileName(AppPath);
string newExeLocation = Path.Combine(packApp.AppInfo.Package.InstalledPath, filename);
_icon = Icon.ExtractAssociatedIcon(newExeLocation);
}
else
{
_icon = Icon.ExtractAssociatedIcon(AppPath);
}
}
catch (Exception)
catch (Exception e)
{
Logger.LogError($"Exception while extracting icon from app path: {AppPath}. Exception message: {e.Message}");
_icon = new Icon(@"images\DefaultIcon.ico");
}
}
@@ -89,6 +105,31 @@ namespace ProjectsEditor.Models
}
}
public async Task<AppListEntry> GetAppByPackageFamilyNameAsync()
{
var pkgManager = new PackageManager();
var pkg = pkgManager.FindPackagesForUser(string.Empty, PackagedId).FirstOrDefault();
if (pkg == null)
{
return null;
}
var apps = await pkg.GetAppListEntriesAsync();
if (apps == null || apps.Count == 0)
{
return null;
}
AppListEntry firstApp = apps[0];
// RandomAccessStreamReference stream = firstApp.AppInfo.DisplayInfo.GetLogo(new Windows.Foundation.Size(64, 64));
// IRandomAccessStreamWithContentType content = await stream.OpenReadAsync();
// BitmapImage bitmapImage = new BitmapImage();
// bitmapImage.StreamSource = (Stream)content;
return firstApp;
}
private BitmapImage _iconBitmapImage;
public BitmapImage IconBitmapImage
@@ -124,9 +165,9 @@ namespace ProjectsEditor.Models
_iconBitmapImage = bitmapImage;
}
}
catch (Exception)
catch (Exception e)
{
// todo
Logger.LogError($"Exception while drawing icon for app with path: {AppPath}. Exception message: {e.Message}");
}
}

View File

@@ -14,6 +14,7 @@ using System.Linq;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;
using ManagedCommon;
using ProjectsEditor.Utils;
namespace ProjectsEditor.Models
@@ -275,8 +276,9 @@ namespace ProjectsEditor.Models
{
graphics.DrawIcon(app.Icon, new Rectangle(32 * appIndex, 0, 24, 24));
}
catch (Exception)
catch (Exception e)
{
Logger.LogError($"Exception while drawing the icon for app {Name}. Exception message: {e.Message}");
}
appIndex++;
@@ -315,5 +317,27 @@ namespace ProjectsEditor.Models
return new Rectangle((int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY));
}
internal string GetShortcutChars()
{
if (string.IsNullOrEmpty(Name))
{
return "PR";
}
string[] words = Name.Trim().ToUpperInvariant().Split(' ');
if (words.Length > 2)
{
return $"{words[0][0]}{words[1][0]}{words[2][0]}";
}
else if (words.Length == 2)
{
return $"{words[0][0]}{words[1][0]}";
}
else
{
return words[0].Substring(0, Math.Min(3, words[0].Length));
}
}
}
}

View File

@@ -161,6 +161,7 @@
</Page.Resources>
<Grid Margin="40,0,40,40">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
@@ -190,55 +191,56 @@
GotFocus="EditNameTextBox_GotFocus"
KeyDown="EditNameTextBoxKeyDown" />
</StackPanel>
<Border
Grid.Row="2"
HorizontalAlignment="Stretch"
Background="{DynamicResource MonitorViewBackgroundBrush}"
CornerRadius="5">
<ScrollViewer
HorizontalAlignment="Center"
HorizontalScrollBarVisibility="Auto">
<ItemsControl
ItemsSource="{Binding Monitors, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel
IsItemsHost="True"
Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="models:MonitorSetup">
<Grid
Margin="20,20,20,20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding MonitorInfoWithResolution}" Foreground="{DynamicResource PrimaryForegroundBrush}" FontSize="16" FontWeight="Normal" Margin="0,5,0,5"/>
<Border
Grid.Row="1"
CornerRadius="5"
BorderBrush="{DynamicResource TertiaryBackgroundBrush}"
BorderThickness="2"
Margin="0,0,0,10">
<Image
Width="200"
Height="140"
Source="{Binding PreviewImage, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
Stretch="Fill"
Margin="2"/>
</Border>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Border>
<ScrollViewer
Margin="0,10,0,0"
VerticalScrollBarVisibility="Auto"
Grid.Row="2">
Grid.Row="3">
<StackPanel Orientation="Vertical">
<Border
Grid.Row="2"
HorizontalAlignment="Stretch"
Background="{DynamicResource MonitorViewBackgroundBrush}"
CornerRadius="5">
<ScrollViewer
HorizontalAlignment="Center"
HorizontalScrollBarVisibility="Auto">
<ItemsControl
ItemsSource="{Binding Monitors, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel
IsItemsHost="True"
Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="models:MonitorSetup">
<Grid
Margin="20,20,20,20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding MonitorInfoWithResolution}" Foreground="{DynamicResource PrimaryForegroundBrush}" FontSize="16" FontWeight="Normal" Margin="0,5,0,5"/>
<Border
Grid.Row="1"
CornerRadius="5"
BorderBrush="{DynamicResource TertiaryBackgroundBrush}"
BorderThickness="2"
Margin="0,0,0,10">
<Image
Width="200"
Height="140"
Source="{Binding PreviewImage, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
Stretch="Fill"
Margin="2"/>
</Border>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Border>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
@@ -251,7 +253,7 @@
</Grid>
</StackPanel>
</ScrollViewer>
<DockPanel Grid.Row="3" Margin="0,20,0,20">
<DockPanel Grid.Row="4" Margin="0,20,0,20">
<CheckBox
DockPanel.Dock="Left"
Content="{x:Static props:Resources.CreateShortcut}"
@@ -266,17 +268,16 @@
x:Name="CancelButton"
Margin="20,0,0,0"
Height="36"
Background="{DynamicResource SecondaryBackgroundBrush}"
AutomationProperties.Name="{x:Static props:Resources.Cancel}"
Click="CancelButtonClicked">
<StackPanel Orientation="Horizontal" Margin="12, 2, 12, 0" >
<TextBlock
AutomationProperties.Name="{x:Static props:Resources.Cancel}"
FontFamily="{DynamicResource SymbolThemeFontFamily}"
Foreground="{DynamicResource AccentButtonForeground}"
Text="&#xE711;" />
<TextBlock
Margin="12,-4,0,0"
Foreground="{DynamicResource AccentButtonForeground}"
Text="{x:Static props:Resources.Cancel}" />
</StackPanel>
<Button.Effect>

View File

@@ -84,6 +84,12 @@
<PackageReference Include="ModernWpfUI" />
<PackageReference Include="System.IO.Abstractions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>

View File

@@ -14,12 +14,43 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using ModernWpf.Media.Animation;
using ProjectsEditor.Models;
namespace ProjectsEditor.Utils
{
public class DrawHelper
{
private const int IconSize = 128;
private static Font font = new("Tahoma", 24);
private static List<Brush> iconBrushes = new List<Brush>
{
////Brushes.Gold,
////Brushes.SteelBlue,
////Brushes.SkyBlue,
////Brushes.DarkGoldenrod,
////Brushes.ForestGreen,
////Brushes.Peru,
////Brushes.Chartreuse,
////Brushes.LightPink,
////Brushes.CadetBlue,
////Brushes.DarkSalmon,
////Brushes.Orange,
////Brushes.DarkSeaGreen,
////Brushes.Yellow,
////Brushes.Green,
////Brushes.Orange,
////Brushes.White,
new SolidBrush(Color.FromArgb(255, 40, 101, 120)),
new SolidBrush(Color.FromArgb(255, 58, 91, 153)),
new SolidBrush(Color.FromArgb(255, 87, 88, 163)),
new SolidBrush(Color.FromArgb(255, 116, 87, 160)),
new SolidBrush(Color.FromArgb(255, 139, 82, 145)),
};
private static int iconBrushIndex;
public static BitmapImage DrawPreview(Project project, Rectangle bounds)
{
double scale = 0.1;
@@ -132,7 +163,6 @@ namespace ProjectsEditor.Utils
if (app.RepeatIndex > 0)
{
string indexString = app.RepeatIndex.ToString(CultureInfo.InvariantCulture);
System.Drawing.Font font = new System.Drawing.Font("Tahoma", 8);
int indexSize = (int)(iconBounds.Width * 0.5);
Rectangle indexBounds = new Rectangle(iconBounds.Right - indexSize, iconBounds.Bottom - indexSize, indexSize, indexSize);
@@ -194,7 +224,6 @@ namespace ProjectsEditor.Utils
if (app.RepeatIndex > 0)
{
string indexString = app.RepeatIndex.ToString(CultureInfo.InvariantCulture);
System.Drawing.Font font = new System.Drawing.Font("Tahoma", 8);
int indexSize = (int)(iconBounds.Width * 0.5);
Rectangle indexBounds = new Rectangle(iconBounds.Right - indexSize, iconBounds.Bottom - indexSize, indexSize, indexSize);
@@ -247,5 +276,169 @@ namespace ProjectsEditor.Utils
path.CloseFigure();
return path;
}
internal static string CreateShortcutIcon(Project project, out Bitmap bitmap)
{
object shDesktop = (object)"Desktop";
IWshRuntimeLibrary.WshShell shell = new IWshRuntimeLibrary.WshShell();
string shortcutIconFilename = (string)shell.SpecialFolders.Item(ref shDesktop) + $"\\{project.Name}.ico";
bitmap = new Bitmap(IconSize, IconSize);
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
// if (project != null)
// {
// List<Application> selectedApps = project.Applications.Where(x => x.IsSelected).ToList();
// if (selectedApps.Count > 0)
// {
// graphics.DrawIcon(selectedApps[0].Icon, new Rectangle(0, 0, IconSize / 2, IconSize / 2));
// }
// if (selectedApps.Count > 1)
// {
// graphics.DrawIcon(selectedApps[1].Icon, new Rectangle(IconSize / 2, 0, IconSize / 2, IconSize / 2));
// }
// if (selectedApps.Count > 2)
// {
// graphics.DrawIcon(selectedApps[2].Icon, new Rectangle(0, IconSize / 2, IconSize / 2, IconSize / 2));
// }
// if (selectedApps.Count > 3)
// {
// graphics.DrawIcon(selectedApps[3].Icon, new Rectangle(IconSize / 2, IconSize / 2, IconSize / 2, IconSize / 2));
// }
// }
// graphics.FillRectangle(new System.Drawing.SolidBrush(Color.FromArgb(128, 32, 32, 32)), 0, 0, IconSize, IconSize);
graphics.FillEllipse(iconBrushes[iconBrushIndex], 0, 0, IconSize, IconSize);
string shortcutChars = "PR";
if (project != null)
{
shortcutChars = project.GetShortcutChars();
}
Rectangle indexBounds;
if (shortcutChars.Length > 1)
{
indexBounds = new Rectangle(0, 0, IconSize, IconSize);
}
else
{
indexBounds = new Rectangle(IconSize / 4, 0, IconSize / 2, IconSize);
}
var textSize = graphics.MeasureString(shortcutChars, font);
var state = graphics.Save();
graphics.TranslateTransform(indexBounds.Left, indexBounds.Top);
graphics.ScaleTransform(indexBounds.Width / textSize.Width, indexBounds.Height / textSize.Height);
graphics.DrawString(shortcutChars, font, Brushes.White, PointF.Empty);
graphics.Restore(state);
iconBrushIndex++;
if (iconBrushIndex >= iconBrushes.Count)
{
iconBrushIndex = 0;
}
}
FileStream fileStream = new FileStream(shortcutIconFilename, FileMode.OpenOrCreate);
using (var memoryStream = new MemoryStream())
{
bitmap.Save(memoryStream, ImageFormat.Png);
BinaryWriter iconWriter = new BinaryWriter(fileStream);
if (fileStream != null && iconWriter != null)
{
// 0-1 reserved, 0
iconWriter.Write((byte)0);
iconWriter.Write((byte)0);
// 2-3 image type, 1 = icon, 2 = cursor
iconWriter.Write((short)1);
// 4-5 number of images
iconWriter.Write((short)1);
// image entry 1
// 0 image width
iconWriter.Write((byte)IconSize);
// 1 image height
iconWriter.Write((byte)IconSize);
// 2 number of colors
iconWriter.Write((byte)0);
// 3 reserved
iconWriter.Write((byte)0);
// 4-5 color planes
iconWriter.Write((short)0);
// 6-7 bits per pixel
iconWriter.Write((short)32);
// 8-11 size of image data
iconWriter.Write((int)memoryStream.Length);
// 12-15 offset of image data
iconWriter.Write((int)(6 + 16));
// write image data
// png data must contain the whole png data file
iconWriter.Write(memoryStream.ToArray());
iconWriter.Flush();
}
}
fileStream.Flush();
fileStream.Close();
return shortcutIconFilename;
}
private static void CreateExamples(Project project)
{
Bitmap bitmap = new Bitmap(IconSize + 1000, IconSize * iconBrushes.Count);
using (Graphics graphics = Graphics.FromImage(bitmap))
{
for (int brushIndex = 0; brushIndex < iconBrushes.Count; brushIndex++)
{
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.FillEllipse(iconBrushes[brushIndex], 0, IconSize * brushIndex, IconSize, IconSize);
string shortcutChars = "PR";
Rectangle indexBounds;
indexBounds = new Rectangle(0, IconSize * brushIndex, IconSize, IconSize);
var textSize = graphics.MeasureString(shortcutChars, font);
var state = graphics.Save();
graphics.TranslateTransform(indexBounds.Left, indexBounds.Top);
graphics.ScaleTransform(indexBounds.Width / textSize.Width, indexBounds.Height / textSize.Height);
graphics.DrawString(shortcutChars, font, Brushes.Black, 0, 0);
graphics.Restore(state);
var b = (SolidBrush)iconBrushes[brushIndex];
var colorName = (from p in typeof(System.Drawing.Color).GetProperties()
where p.PropertyType.Equals(typeof(System.Drawing.Color))
let value = (System.Drawing.Color)p.GetValue(null, null)
where value.R == b.Color.R &&
value.G == b.Color.G &&
value.B == b.Color.B &&
value.A == b.Color.A
select p.Name).DefaultIfEmpty("unknown").First();
graphics.DrawString(colorName, font, Brushes.White, IconSize, IconSize * brushIndex);
}
}
bitmap.Save(@"C:\temp\shortcutIcons.png");
}
}
}

View File

@@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Windows;
using ManagedCommon;
using ProjectsEditor.Data;
using ProjectsEditor.Models;
using ProjectsEditor.ViewModels;
@@ -25,12 +26,14 @@ namespace ProjectsEditor.Utils
ProjectsData parser = new ProjectsData();
if (!File.Exists(parser.File))
{
Logger.LogWarning($"Projects storage file not found: {parser.File}");
return new ParsingResult(true);
}
ProjectsData.ProjectsListWrapper projects = parser.Read(parser.File);
if (!SetProjects(mainViewModel, projects))
{
Logger.LogWarning($"Projects storage file content could not be set. Reason: {Properties.Resources.Error_Parsing_Message}");
return new ParsingResult(false, ProjectsEditor.Properties.Resources.Error_Parsing_Message);
}
@@ -38,6 +41,7 @@ namespace ProjectsEditor.Utils
}
catch (Exception e)
{
Logger.LogError($"Exception while parsing storage file: {e.Message}");
return new ParsingResult(false, e.Message);
}
}
@@ -50,12 +54,14 @@ namespace ProjectsEditor.Utils
ProjectsData parser = new ProjectsData();
if (!File.Exists(fileName))
{
Logger.LogWarning($"ParseProject method. Projects storage file not found: {parser.File}");
return new ParsingResult(true);
}
ProjectsData.ProjectsListWrapper projects = parser.Read(fileName);
if (!ExtractProject(projects, out project))
{
Logger.LogWarning($"ParseProject method. Projects storage file content could not be set. Reason: {Properties.Resources.Error_Parsing_Message}");
return new ParsingResult(false, ProjectsEditor.Properties.Resources.Error_Parsing_Message);
}
@@ -63,6 +69,7 @@ namespace ProjectsEditor.Utils
}
catch (Exception e)
{
Logger.LogError($"ParseProject method. Exception while parsing storage file: {e.Message}");
return new ParsingResult(false, e.Message);
}
}
@@ -209,9 +216,10 @@ namespace ProjectsEditor.Utils
IOUtils ioUtils = new IOUtils();
ioUtils.WriteFile(serializer.File, serializer.Serialize(projectsWrapper));
}
catch (Exception)
catch (Exception e)
{
// TODO: show error
Logger.LogError($"Exception while writing storage file: {e.Message}");
}
}

View File

@@ -7,10 +7,14 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Timers;
using System.Windows.Media.Imaging;
using ManagedCommon;
using ProjectsEditor.Models;
using ProjectsEditor.Utils;
using static ProjectsEditor.Data.ProjectsData;
@@ -130,17 +134,19 @@ namespace ProjectsEditor.ViewModels
}
}
private void CreateShortcut(Project editedProject)
private void CreateShortcut(Project project)
{
object shDesktop = (object)"Desktop";
IWshRuntimeLibrary.WshShell shell = new IWshRuntimeLibrary.WshShell();
string shortcutAddress = (string)shell.SpecialFolders.Item(ref shDesktop) + $"\\{editedProject.Name}.lnk";
string shortcutAddress = (string)shell.SpecialFolders.Item(ref shDesktop) + $"\\{project.Name}.lnk";
IWshRuntimeLibrary.IWshShortcut shortcut = (IWshRuntimeLibrary.IWshShortcut)shell.CreateShortcut(shortcutAddress);
shortcut.Description = $"Project Launcher {editedProject.Id}";
shortcut.Description = $"Project Launcher {project.Id}";
string basePath = AppDomain.CurrentDomain.BaseDirectory;
shortcut.TargetPath = Path.Combine(basePath, "ProjectsEditor.exe");
shortcut.Arguments = '"' + editedProject.Id + '"';
shortcut.TargetPath = Path.Combine(basePath, "ProjectsLauncher.exe");
shortcut.Arguments = '"' + project.Id + '"';
shortcut.WorkingDirectory = basePath;
string iconFilename = DrawHelper.CreateShortcutIcon(project, out Bitmap bitmap);
shortcut.IconLocation = iconFilename;
shortcut.Save();
}
@@ -221,6 +227,7 @@ namespace ProjectsEditor.ViewModels
{
if (!Projects.Where(x => x.Id == projectId).Any())
{
Logger.LogWarning($"Project to launch not find. Id: {projectId}");
return;
}
@@ -237,6 +244,7 @@ namespace ProjectsEditor.ViewModels
if (exitAfterLaunch)
{
Logger.LogInfo($"Launched the project {project.Name}. Exiting.");
Environment.Exit(0);
}
}

View File

@@ -144,19 +144,22 @@ namespace Utils
};
}
std::wstring installPathUpper(appData.installPath);
std::transform(installPathUpper.begin(), installPathUpper.end(), installPathUpper.begin(), towupper);
if (appPathUpper.contains(installPathUpper))
if (!appData.installPath.empty())
{
return appData;
}
std::wstring installPathUpper(appData.installPath);
std::transform(installPathUpper.begin(), installPathUpper.end(), installPathUpper.begin(), towupper);
// edge case, some apps (e.g., Gitkraken) have different .exe files in the subfolders.
// apps list contains only one path, so in this case app is not found
if (std::filesystem::path(appPath).filename() == std::filesystem::path(appData.installPath).filename())
{
return appData;
if (appPathUpper.contains(installPathUpper))
{
return appData;
}
// edge case, some apps (e.g., Gitkraken) have different .exe files in the subfolders.
// apps list contains only one path, so in this case app is not found
if (std::filesystem::path(appPath).filename() == std::filesystem::path(appData.installPath).filename())
{
return appData;
}
}
}

View File

@@ -124,7 +124,7 @@ int main(int argc, char* argv[])
Project::Application app {
.name = data.value().name,
.title = title,
.path = data.value().installPath,
.path = processPath,
.packageFullName = data.value().packageFullName,
.commandLineArgs = L"",
.isMinimized = WindowUtils::IsMinimized(window),