[Workspaces] implement the move feature (#35480)

* [Workspaces] Add move functionality

* spell checker

* [Workspaces] Modify Arranger to move apps without launch

* moved ipc helper

* removed callback

* use LauncherStatus in WindowArranger

* wait for launching next app

* launch in a separate thread and protect by mutexes

* update app version in advance

* changed canceling launch

* increased waiting time

* Fix optional parameter load from json

* changed arranger waiting time

* additional waiting time for Outlook

* added app id

* ensure ids before launch

* set id in editor

* minor updates

* [Workspaces] Move: Get the nearest window when moving a window

* [Workspaces] convert optional boolean to enum to avoid json problems

* Handle case when the new Application Property "moveIfExists" does not exist

* Re-implementing app-window pairing for moving feature.

* spell checker

* XAML formatting

* Fixing bug: IPC message not arriving

* spell checker

* Removing app-level-setting for move app. Also fixed compiler errors due styling.

* Updating editor window layout

* Re-implementing window positioning UI elements

* XAML formatting

* Code review findings

* Code cleanup

* Code cleanup

* Code cleanup

* code cleanup

* Code cleanup

* Code cleanup

* fix Move attribute after launch and snapshot

* Extend WindowArranger with PWA functionality to detect different PWA apps. PwaHelper moved to the common library

* fix repeat counter in the editor

* Code optimization

* code cleanup, optimization

* fix double-processing window

---------

Co-authored-by: Seraphima <zykovas91@gmail.com>
Co-authored-by: donlaci <donlaci@yahoo.com>
This commit is contained in:
Laszlo Nemeth
2024-12-04 18:17:54 +01:00
committed by GitHub
parent e0949cb4ea
commit 89be43e15d
38 changed files with 670 additions and 558 deletions

View File

@@ -18,14 +18,7 @@ namespace WorkspacesEditor.Models
public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
{
if (item is MonitorHeaderRow)
{
return HeaderTemplate;
}
else
{
return AppTemplate;
}
return item is MonitorHeaderRow ? HeaderTemplate : AppTemplate;
}
}
}

View File

@@ -1,25 +1,22 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using System.Windows.Media.Imaging;
using ManagedCommon;
using Windows.Management.Deployment;
using WorkspacesCsharpLibrary;
using WorkspacesCsharpLibrary.Models;
namespace WorkspacesEditor.Models
{
public enum WindowPositionKind
{
Custom = 0,
Maximized = 1,
Minimized = 2,
}
public class Application : BaseApplication, IDisposable
{
private bool _isInitialized;
@@ -79,7 +76,7 @@ namespace WorkspacesEditor.Models
return left.X != right.X || left.Y != right.Y || left.Width != right.Width || left.Height != right.Height;
}
public override bool Equals(object obj)
public override readonly bool Equals(object obj)
{
if (obj == null || GetType() != obj.GetType())
{
@@ -90,7 +87,7 @@ namespace WorkspacesEditor.Models
return X == pos.X && Y == pos.Y && Width == pos.Width && Height == pos.Height;
}
public override int GetHashCode()
public override readonly int GetHashCode()
{
return base.GetHashCode();
}
@@ -136,36 +133,24 @@ namespace WorkspacesEditor.Models
}
}
private bool _minimized;
public bool Minimized { get; set; }
public bool Minimized
public bool Maximized { get; set; }
public bool EditPositionEnabled => !Minimized && !Maximized;
public int PositionComboboxIndex
{
get => _minimized;
get => Maximized ? (int)WindowPositionKind.Maximized : Minimized ? (int)WindowPositionKind.Minimized : (int)WindowPositionKind.Custom;
set
{
_minimized = value;
OnPropertyChanged(new PropertyChangedEventArgs(nameof(Minimized)));
Maximized = value == (int)WindowPositionKind.Maximized;
Minimized = value == (int)WindowPositionKind.Minimized;
OnPropertyChanged(new PropertyChangedEventArgs(nameof(EditPositionEnabled)));
RedrawPreviewImage();
}
}
private bool _maximized;
public bool Maximized
{
get => _maximized;
set
{
_maximized = value;
OnPropertyChanged(new PropertyChangedEventArgs(nameof(Maximized)));
OnPropertyChanged(new PropertyChangedEventArgs(nameof(EditPositionEnabled)));
RedrawPreviewImage();
}
}
public bool EditPositionEnabled { get => !Minimized && !Maximized; }
private string _appMainParams;
public string AppMainParams
@@ -183,7 +168,7 @@ namespace WorkspacesEditor.Models
}
}
public bool IsAppMainParamVisible { get => !string.IsNullOrWhiteSpace(_appMainParams); }
public bool IsAppMainParamVisible => !string.IsNullOrWhiteSpace(_appMainParams);
[JsonIgnore]
public bool IsHighlighted { get; set; }
@@ -192,13 +177,7 @@ namespace WorkspacesEditor.Models
public int RepeatIndex { get; set; }
[JsonIgnore]
public string RepeatIndexString
{
get
{
return RepeatIndex <= 1 ? string.Empty : RepeatIndex.ToString(CultureInfo.InvariantCulture);
}
}
public string RepeatIndexString => RepeatIndex <= 1 ? string.Empty : RepeatIndex.ToString(CultureInfo.InvariantCulture);
private WindowPosition _position;
@@ -242,10 +221,7 @@ namespace WorkspacesEditor.Models
{
get
{
if (_monitorSetup == null)
{
_monitorSetup = Parent.GetMonitorForApp(this);
}
_monitorSetup ??= Parent.GetMonitorForApp(this);
return _monitorSetup;
}
@@ -271,7 +247,7 @@ namespace WorkspacesEditor.Models
}
}
public string DeleteButtonContent { get => _isIncluded ? Properties.Resources.Delete : Properties.Resources.AddBack; }
public string DeleteButtonContent => _isIncluded ? Properties.Resources.Delete : Properties.Resources.AddBack;
private bool _isIncluded = true;
@@ -298,15 +274,5 @@ namespace WorkspacesEditor.Models
CommandLineArguments = newCommandLineValue;
OnPropertyChanged(new PropertyChangedEventArgs(nameof(AppMainParams)));
}
internal void MaximizedChecked()
{
Minimized = false;
}
internal void MinimizedChecked()
{
Maximized = false;
}
}
}

View File

@@ -6,28 +6,18 @@ using System.Windows;
namespace WorkspacesEditor.Models
{
public class Monitor
public class Monitor(string monitorName, string monitorInstanceId, int number, int dpi, Rect dpiAwareBounds, Rect dpiUnawareBounds)
{
public string MonitorName { get; private set; }
public string MonitorName { get; private set; } = monitorName;
public string MonitorInstanceId { get; private set; }
public string MonitorInstanceId { get; private set; } = monitorInstanceId;
public int MonitorNumber { get; private set; }
public int MonitorNumber { get; private set; } = number;
public int Dpi { get; private set; }
public int Dpi { get; private set; } = dpi;
public Rect MonitorDpiUnawareBounds { get; private set; }
public Rect MonitorDpiUnawareBounds { get; private set; } = dpiUnawareBounds;
public Rect MonitorDpiAwareBounds { get; private set; }
public Monitor(string monitorName, string monitorInstanceId, int number, int dpi, Rect dpiAwareBounds, Rect dpiUnawareBounds)
{
MonitorName = monitorName;
MonitorInstanceId = monitorInstanceId;
MonitorNumber = number;
Dpi = dpi;
MonitorDpiAwareBounds = dpiAwareBounds;
MonitorDpiUnawareBounds = dpiUnawareBounds;
}
public Rect MonitorDpiAwareBounds { get; private set; } = dpiAwareBounds;
}
}

View File

@@ -16,9 +16,9 @@ namespace WorkspacesEditor.Models
PropertyChanged?.Invoke(this, e);
}
public string MonitorInfo { get => MonitorName; }
public string MonitorInfo => MonitorName;
public string MonitorInfoWithResolution { get => $"{MonitorName} {MonitorDpiAwareBounds.Width}x{MonitorDpiAwareBounds.Height}"; }
public string MonitorInfoWithResolution => $"{MonitorName} {MonitorDpiAwareBounds.Width}x{MonitorDpiAwareBounds.Height}";
public MonitorSetup(string monitorName, string monitorInstanceId, int number, int dpi, Rect dpiAwareBounds, Rect dpiUnawareBounds)
: base(monitorName, monitorInstanceId, number, dpi, dpiAwareBounds, dpiUnawareBounds)

View File

@@ -29,10 +29,7 @@ namespace WorkspacesEditor.Models
public string Name
{
get
{
return _name;
}
get => _name;
set
{
@@ -68,8 +65,7 @@ namespace WorkspacesEditor.Models
DateTime lastLaunchDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(LastLaunchedTime);
var now = DateTime.UtcNow.Ticks;
var ts = DateTime.UtcNow - lastLaunchDateTime;
TimeSpan ts = DateTime.UtcNow - lastLaunchDateTime;
double delta = Math.Abs(ts.TotalSeconds);
if (delta < 1 * MINUTE)
@@ -120,10 +116,7 @@ namespace WorkspacesEditor.Models
}
}
public bool CanBeSaved
{
get => Name.Length > 0 && Applications.Count > 0;
}
public bool CanBeSaved => Name.Length > 0 && Applications.Count > 0;
private bool _isRevertEnabled;
@@ -145,10 +138,7 @@ namespace WorkspacesEditor.Models
[JsonIgnore]
public bool IsPopupVisible
{
get
{
return _isPopupVisible;
}
get => _isPopupVisible;
set
{
@@ -163,11 +153,11 @@ namespace WorkspacesEditor.Models
{
get
{
List<object> applicationsListed = new List<object>();
List<object> applicationsListed = [];
ILookup<MonitorSetup, Application> apps = Applications.Where(x => !x.Minimized).ToLookup(x => x.MonitorSetup);
foreach (var appItem in apps.OrderBy(x => x.Key.MonitorDpiUnawareBounds.Left).ThenBy(x => x.Key.MonitorDpiUnawareBounds.Top))
foreach (IGrouping<MonitorSetup, Application> appItem in apps.OrderBy(x => x.Key.MonitorDpiUnawareBounds.Left).ThenBy(x => x.Key.MonitorDpiUnawareBounds.Top))
{
MonitorHeaderRow headerRow = new MonitorHeaderRow { MonitorName = "Screen " + appItem.Key.MonitorNumber, SelectString = Properties.Resources.SelectAllAppsOnMonitor + " " + appItem.Key.MonitorInfo };
MonitorHeaderRow headerRow = new() { MonitorName = "Screen " + appItem.Key.MonitorNumber, SelectString = Properties.Resources.SelectAllAppsOnMonitor + " " + appItem.Key.MonitorInfo };
applicationsListed.Add(headerRow);
foreach (Application app in appItem)
{
@@ -175,10 +165,10 @@ namespace WorkspacesEditor.Models
}
}
var minimizedApps = Applications.Where(x => x.Minimized);
IEnumerable<Application> minimizedApps = Applications.Where(x => x.Minimized);
if (minimizedApps.Any())
{
MonitorHeaderRow headerRow = new MonitorHeaderRow { MonitorName = Properties.Resources.Minimized_Apps, SelectString = Properties.Resources.SelectAllMinimizedApps };
MonitorHeaderRow headerRow = new() { MonitorName = Properties.Resources.Minimized_Apps, SelectString = Properties.Resources.SelectAllMinimizedApps };
applicationsListed.Add(headerRow);
foreach (Application app in minimizedApps)
{
@@ -219,17 +209,17 @@ namespace WorkspacesEditor.Models
int screenIndex = 1;
Monitors = new List<MonitorSetup>();
foreach (var item in selectedProject.Monitors.OrderBy(x => x.MonitorDpiAwareBounds.Left).ThenBy(x => x.MonitorDpiAwareBounds.Top))
Monitors = [];
foreach (MonitorSetup item in selectedProject.Monitors.OrderBy(x => x.MonitorDpiAwareBounds.Left).ThenBy(x => x.MonitorDpiAwareBounds.Top))
{
Monitors.Add(item);
screenIndex++;
}
Applications = new List<Application>();
foreach (var item in selectedProject.Applications)
Applications = [];
foreach (Application item in selectedProject.Applications)
{
Application newApp = new Application(item);
Application newApp = new(item);
newApp.Parent = this;
newApp.InitializationFinished();
Applications.Add(newApp);
@@ -244,14 +234,14 @@ namespace WorkspacesEditor.Models
LastLaunchedTime = project.LastLaunchedTime;
IsShortcutNeeded = project.IsShortcutNeeded;
MoveExistingWindows = project.MoveExistingWindows;
Monitors = new List<MonitorSetup>() { };
Applications = new List<Models.Application> { };
Monitors = [];
Applications = [];
foreach (var app in project.Applications)
foreach (ProjectData.ApplicationWrapper app in project.Applications)
{
Models.Application newApp = new Models.Application()
Models.Application newApp = new()
{
Id = string.IsNullOrEmpty(app.Id) ? $"{{{Guid.NewGuid().ToString()}}}" : app.Id,
Id = string.IsNullOrEmpty(app.Id) ? $"{{{Guid.NewGuid()}}}" : app.Id,
AppName = app.Application,
AppPath = app.ApplicationPath,
AppTitle = app.Title,
@@ -278,20 +268,17 @@ namespace WorkspacesEditor.Models
Applications.Add(newApp);
}
foreach (var monitor in project.MonitorConfiguration)
foreach (ProjectData.MonitorConfigurationWrapper monitor in project.MonitorConfiguration)
{
System.Windows.Rect dpiAware = new System.Windows.Rect(monitor.MonitorRectDpiAware.Left, monitor.MonitorRectDpiAware.Top, monitor.MonitorRectDpiAware.Width, monitor.MonitorRectDpiAware.Height);
System.Windows.Rect dpiUnaware = new System.Windows.Rect(monitor.MonitorRectDpiUnaware.Left, monitor.MonitorRectDpiUnaware.Top, monitor.MonitorRectDpiUnaware.Width, monitor.MonitorRectDpiUnaware.Height);
System.Windows.Rect dpiAware = new(monitor.MonitorRectDpiAware.Left, monitor.MonitorRectDpiAware.Top, monitor.MonitorRectDpiAware.Width, monitor.MonitorRectDpiAware.Height);
System.Windows.Rect dpiUnaware = new(monitor.MonitorRectDpiUnaware.Left, monitor.MonitorRectDpiUnaware.Top, monitor.MonitorRectDpiUnaware.Width, monitor.MonitorRectDpiUnaware.Height);
Monitors.Add(new MonitorSetup(monitor.Id, monitor.InstanceId, monitor.MonitorNumber, monitor.Dpi, dpiAware, dpiUnaware));
}
}
public BitmapImage PreviewIcons
{
get
{
return _previewIcons;
}
get => _previewIcons;
set
{
@@ -302,10 +289,7 @@ namespace WorkspacesEditor.Models
public BitmapImage PreviewImage
{
get
{
return _previewImage;
}
get => _previewImage;
set
{
@@ -316,10 +300,7 @@ namespace WorkspacesEditor.Models
public double PreviewImageWidth
{
get
{
return _previewImageWidth;
}
get => _previewImageWidth;
set
{
@@ -366,6 +347,7 @@ namespace WorkspacesEditor.Models
Id = other.Id;
Name = other.Name;
IsRevertEnabled = true;
MoveExistingWindows = other.MoveExistingWindows;
}
internal void CloseExpanders()
@@ -378,13 +360,13 @@ namespace WorkspacesEditor.Models
internal MonitorSetup GetMonitorForApp(Application app)
{
var monitorSetup = Monitors.Where(x => x.MonitorNumber == app.MonitorNumber).FirstOrDefault();
MonitorSetup monitorSetup = Monitors.Where(x => x.MonitorNumber == app.MonitorNumber).FirstOrDefault();
if (monitorSetup == null)
{
// monitors changed: try to determine monitor id based on middle point
int middleX = app.Position.X + (app.Position.Width / 2);
int middleY = app.Position.Y + (app.Position.Height / 2);
var monitorCandidate = Monitors.Where(x =>
MonitorSetup monitorCandidate = Monitors.Where(x =>
(x.MonitorDpiUnawareBounds.Left < middleX) &&
(x.MonitorDpiUnawareBounds.Right > middleX) &&
(x.MonitorDpiUnawareBounds.Top < middleY) &&