Files
PowerToys/src/modules/Projects/ProjectsEditor/ViewModels/MainViewModel.cs

484 lines
16 KiB
C#
Raw Normal View History

2024-05-21 16:55:15 +02:00
// 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.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
2024-05-21 16:55:15 +02:00
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Timers;
2024-05-28 18:21:30 +02:00
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
2024-07-16 09:37:40 +02:00
using ProjectsEditor.Data;
2024-05-21 16:55:15 +02:00
using ProjectsEditor.Models;
using ProjectsEditor.Utils;
using static ProjectsEditor.Data.ProjectsData;
namespace ProjectsEditor.ViewModels
{
public class MainViewModel : INotifyPropertyChanged, IDisposable
{
private ProjectsEditorIO _projectsEditorIO;
2024-07-16 09:37:40 +02:00
private ProjectEditor editPage;
private SnapshotWindow _snapshotWindow;
private List<OverlayWindow> _overlayWindows = new List<OverlayWindow>();
2024-07-16 09:37:40 +02:00
private bool _isExistingProjectLaunched;
private Project editedProject;
private Project projectBeforeLaunch;
private string projectNameBeingEdited;
private MainWindow _mainWindow;
private Timer lastUpdatedTimer;
private ProjectsSettings settings;
2024-05-21 16:55:15 +02:00
2024-06-12 17:52:28 +02:00
public ObservableCollection<Project> Projects { get; set; } = new ObservableCollection<Project>();
2024-05-21 16:55:15 +02:00
public IEnumerable<Project> ProjectsView
{
get
{
IEnumerable<Project> projects = GetFilteredProjects();
IsProjectsViewEmpty = !(projects != null && projects.Any());
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsProjectsViewEmpty)));
if (IsProjectsViewEmpty)
2024-05-21 16:55:15 +02:00
{
if (Projects != null && Projects.Any())
{
EmptyProjectsViewMessage = Properties.Resources.NoProjectsMatch;
}
else
{
EmptyProjectsViewMessage = Properties.Resources.No_Projects_Message;
}
OnPropertyChanged(new PropertyChangedEventArgs(nameof(EmptyProjectsViewMessage)));
2024-05-21 16:55:15 +02:00
return Enumerable.Empty<Project>();
}
OrderBy orderBy = (OrderBy)_orderByIndex;
if (orderBy == OrderBy.LastViewed)
{
return projects.OrderByDescending(x => x.LastLaunchedTime);
}
else if (orderBy == OrderBy.Created)
{
return projects.OrderByDescending(x => x.CreationTime);
}
else
{
return projects.OrderBy(x => x.Name);
}
}
}
public bool IsProjectsViewEmpty { get; set; }
public string EmptyProjectsViewMessage { get; set; }
// return those projects where the project name or any of the selected apps' name contains the search term
private IEnumerable<Project> GetFilteredProjects()
{
if (string.IsNullOrEmpty(_searchTerm))
{
return Projects;
}
return Projects.Where(x =>
{
if (x.Name.Contains(_searchTerm, StringComparison.InvariantCultureIgnoreCase))
{
return true;
}
if (x.Applications == null)
{
return false;
}
return x.Applications.Any(app => app.AppName.Contains(_searchTerm, StringComparison.InvariantCultureIgnoreCase));
});
}
2024-05-21 16:55:15 +02:00
private string _searchTerm;
public string SearchTerm
{
get => _searchTerm;
set
{
_searchTerm = value;
OnPropertyChanged(new PropertyChangedEventArgs(nameof(ProjectsView)));
}
}
private int _orderByIndex;
public int OrderByIndex
{
get => _orderByIndex;
set
{
_orderByIndex = value;
OnPropertyChanged(new PropertyChangedEventArgs(nameof(ProjectsView)));
settings.Properties.SortBy = (ProjectsProperties.SortByProperty)value;
settings.Save(new SettingsUtils());
2024-05-21 16:55:15 +02:00
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged?.Invoke(this, e);
}
public MainViewModel(ProjectsEditorIO projectsEditorIO)
{
settings = Utils.Settings.ReadSettings();
_orderByIndex = (int)settings.Properties.SortBy;
2024-05-21 16:55:15 +02:00
_projectsEditorIO = projectsEditorIO;
lastUpdatedTimer = new System.Timers.Timer();
lastUpdatedTimer.Interval = 1000;
lastUpdatedTimer.Elapsed += LastUpdatedTimerElapsed;
lastUpdatedTimer.Start();
}
public void Initialize()
{
foreach (Project project in Projects)
{
project.Initialize();
}
OnPropertyChanged(new PropertyChangedEventArgs(nameof(ProjectsView)));
}
public void SetEditedProject(Project editedProject)
{
this.editedProject = editedProject;
}
public void SaveProject(Project projectToSave)
{
editedProject.Name = projectToSave.Name;
editedProject.IsShortcutNeeded = projectToSave.IsShortcutNeeded;
editedProject.PreviewIcons = projectToSave.PreviewIcons;
2024-05-21 16:55:15 +02:00
editedProject.PreviewImage = projectToSave.PreviewImage;
editedProject.Applications = projectToSave.Applications.Where(x => x.IsIncluded).ToList();
2024-05-21 16:55:15 +02:00
editedProject.OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs("AppsCountString"));
editedProject.Initialize();
_projectsEditorIO.SerializeProjects(Projects.ToList());
2024-07-18 12:50:38 +02:00
ApplyShortcut(editedProject);
2024-05-21 16:55:15 +02:00
}
2024-07-18 12:50:38 +02:00
private void ApplyShortcut(Project project)
2024-05-21 16:55:15 +02:00
{
2024-06-12 17:52:04 +02:00
string basePath = AppDomain.CurrentDomain.BaseDirectory;
2024-06-19 00:06:57 +02:00
string shortcutAddress = Path.Combine(FolderUtils.Desktop(), project.Name + ".lnk");
string shortcutIconFilename = Path.Combine(FolderUtils.Temp(), project.Id + ".ico");
2024-06-12 17:52:04 +02:00
2024-07-18 12:50:38 +02:00
if (!project.IsShortcutNeeded)
{
if (File.Exists(shortcutIconFilename))
{
File.Delete(shortcutIconFilename);
}
if (File.Exists(shortcutAddress))
{
File.Delete(shortcutAddress);
}
return;
}
2024-06-12 17:52:04 +02:00
Bitmap icon = ProjectIcon.DrawIcon(ProjectIcon.IconTextFromProjectName(project.Name));
ProjectIcon.SaveIcon(icon, shortcutIconFilename);
2024-06-19 00:06:57 +02:00
try
{
// Workaround to be able to create a shortcut with unicode filename
File.WriteAllBytes(shortcutAddress, Array.Empty<byte>());
// Create a ShellLinkObject that references the .lnk file
2024-06-19 11:16:58 +02:00
Shell32.Shell shell = new Shell32.Shell();
Shell32.Folder dir = shell.NameSpace(FolderUtils.Desktop());
Shell32.FolderItem folderItem = dir.Items().Item($"{project.Name}.lnk");
Shell32.ShellLinkObject link = (Shell32.ShellLinkObject)folderItem.GetLink;
2024-06-19 00:06:57 +02:00
// Set the .lnk file properties
2024-06-19 11:16:58 +02:00
link.Description = $"Project Launcher {project.Id}";
link.Path = Path.Combine(basePath, "PowerToys.ProjectsLauncher.exe");
link.Arguments = project.Id.ToString();
link.WorkingDirectory = basePath;
link.SetIconLocation(shortcutIconFilename, 0);
2024-06-19 00:06:57 +02:00
2024-06-19 11:16:58 +02:00
link.Save(shortcutAddress);
2024-06-19 00:06:57 +02:00
}
catch (Exception ex)
{
Logger.LogError($"Shortcut creation error: {ex.Message}");
}
2024-05-21 16:55:15 +02:00
}
public void SaveProjectName(Project project)
{
projectNameBeingEdited = project.Name;
}
public void CancelProjectName(Project project)
{
project.Name = projectNameBeingEdited;
}
public async void SnapNewProject()
2024-05-21 16:55:15 +02:00
{
CancelSnapshot();
2024-05-21 16:55:15 +02:00
await Task.Run(() => RunSnapshotTool());
Project project = new Project();
if (_projectsEditorIO.ParseTempProject(out project).Result)
{
2024-07-16 09:37:40 +02:00
if (_isExistingProjectLaunched)
{
UpdateProject(project);
}
else
{
EditProject(project, true);
}
2024-05-21 16:55:15 +02:00
}
}
2024-07-16 09:37:40 +02:00
private void UpdateProject(Project project)
{
project.Name = projectBeforeLaunch.Name;
project.IsRevertEnabled = true;
2024-07-18 12:50:38 +02:00
CheckShortcutPresence(project);
2024-07-16 09:37:40 +02:00
editPage.DataContext = project;
2024-07-17 18:48:13 +02:00
project.Initialize();
2024-07-16 09:37:40 +02:00
}
internal void RevertLaunch()
{
2024-07-18 12:50:38 +02:00
CheckShortcutPresence(projectBeforeLaunch);
2024-07-16 09:37:40 +02:00
editPage.DataContext = projectBeforeLaunch;
2024-07-17 18:48:13 +02:00
projectBeforeLaunch.Initialize();
2024-07-16 09:37:40 +02:00
}
2024-05-21 16:55:15 +02:00
public void EditProject(Project selectedProject, bool isNewlyCreated = false)
{
2024-07-16 09:37:40 +02:00
editPage = new ProjectEditor(this);
2024-05-21 16:55:15 +02:00
SetEditedProject(selectedProject);
if (!isNewlyCreated)
{
selectedProject = new Project(selectedProject);
}
2024-05-21 16:55:15 +02:00
if (isNewlyCreated)
{
// generate a default name for the new project
string defaultNamePrefix = Properties.Resources.DefaultProjectNamePrefix;
int nextProjectIndex = 0;
foreach (var proj in Projects)
{
if (proj.Name.StartsWith(defaultNamePrefix, StringComparison.CurrentCulture))
{
try
{
int index = int.Parse(proj.Name[(defaultNamePrefix.Length + 1)..], CultureInfo.CurrentCulture);
if (nextProjectIndex < index)
{
nextProjectIndex = index;
}
}
catch (Exception)
{
}
}
}
selectedProject.Name = defaultNamePrefix + " " + (nextProjectIndex + 1).ToString(CultureInfo.CurrentCulture);
}
selectedProject.EditorWindowTitle = isNewlyCreated ? Properties.Resources.CreateProject : Properties.Resources.EditProject;
selectedProject.Initialize();
2024-07-18 12:50:38 +02:00
CheckShortcutPresence(selectedProject);
editPage.DataContext = selectedProject;
2024-05-21 16:55:15 +02:00
_mainWindow.ShowPage(editPage);
lastUpdatedTimer.Stop();
}
2024-07-18 12:50:38 +02:00
private void CheckShortcutPresence(Project project)
{
string basePath = AppDomain.CurrentDomain.BaseDirectory;
string shortcutAddress = Path.Combine(FolderUtils.Desktop(), project.Name + ".lnk");
project.IsShortcutNeeded = File.Exists(shortcutAddress);
}
public void AddNewProject(Project project)
2024-05-21 16:55:15 +02:00
{
2024-07-17 21:12:09 +02:00
project.Applications.RemoveAll(app => !app.IsIncluded);
project.Initialize();
Projects.Add(project);
_projectsEditorIO.SerializeProjects(Projects.ToList());
OnPropertyChanged(new PropertyChangedEventArgs(nameof(ProjectsView)));
2024-07-18 12:50:38 +02:00
ApplyShortcut(project);
2024-05-21 16:55:15 +02:00
}
public void DeleteProject(Project selectedProject)
{
Projects.Remove(selectedProject);
_projectsEditorIO.SerializeProjects(Projects.ToList());
OnPropertyChanged(new PropertyChangedEventArgs(nameof(ProjectsView)));
}
public void SetMainWindow(MainWindow mainWindow)
{
_mainWindow = mainWindow;
}
public void SwitchToMainView()
{
_mainWindow.SwitchToMainView();
SearchTerm = string.Empty;
OnPropertyChanged(new PropertyChangedEventArgs(nameof(SearchTerm)));
lastUpdatedTimer.Start();
}
public void LaunchProject(string projectId)
{
if (!Projects.Where(x => x.Id == projectId).Any())
{
2024-05-28 18:21:30 +02:00
Logger.LogWarning($"Project to launch not find. Id: {projectId}");
2024-05-21 16:55:15 +02:00
return;
}
LaunchProject(Projects.Where(x => x.Id == projectId).First(), true);
}
public async void LaunchProject(Project project, bool exitAfterLaunch = false)
{
2024-05-27 16:33:30 +02:00
await Task.Run(() => RunLauncher(project.Id));
2024-05-21 16:55:15 +02:00
if (_projectsEditorIO.ParseProjects(this).Result == true)
{
2024-05-27 16:33:30 +02:00
OnPropertyChanged(new PropertyChangedEventArgs(nameof(ProjectsView)));
}
2024-05-21 16:55:15 +02:00
if (exitAfterLaunch)
{
2024-05-28 18:21:30 +02:00
Logger.LogInfo($"Launched the project {project.Name}. Exiting.");
2024-05-21 16:55:15 +02:00
Environment.Exit(0);
}
}
private void LastUpdatedTimerElapsed(object sender, ElapsedEventArgs e)
{
if (Projects == null)
{
return;
}
foreach (Project project in Projects)
{
project.OnPropertyChanged(new PropertyChangedEventArgs("LastLaunched"));
}
}
private void RunSnapshotTool(string filename = null)
{
Process p = new Process();
2024-06-12 17:45:27 +02:00
p.StartInfo = new ProcessStartInfo(@".\PowerToys.ProjectsSnapshotTool.exe");
2024-05-21 16:55:15 +02:00
p.StartInfo.CreateNoWindow = true;
if (!string.IsNullOrEmpty(filename))
{
p.StartInfo.Arguments = filename;
}
p.Start();
p.WaitForExit();
}
2024-07-16 09:37:40 +02:00
private void RunLauncher(string projectIdOrFilename)
2024-05-21 16:55:15 +02:00
{
Process p = new Process();
2024-07-16 09:37:40 +02:00
p.StartInfo = new ProcessStartInfo(@".\PowerToys.ProjectsLauncher.exe", projectIdOrFilename);
2024-05-21 16:55:15 +02:00
p.StartInfo.CreateNoWindow = true;
p.Start();
p.WaitForExit();
}
public void Dispose()
{
GC.SuppressFinalize(this);
}
internal void CloseAllPopups()
{
foreach (Project project in Projects)
{
project.IsPopupVisible = false;
}
}
2024-07-16 09:37:40 +02:00
internal void EnterSnapshotMode(bool isExistingProjectLaunched)
{
2024-07-16 09:37:40 +02:00
_isExistingProjectLaunched = isExistingProjectLaunched;
_mainWindow.WindowState = System.Windows.WindowState.Minimized;
_overlayWindows.Clear();
2024-07-01 19:53:26 +02:00
foreach (var screen in MonitorHelper.GetDpiUnawareScreens())
{
2024-07-01 19:53:26 +02:00
var bounds = screen.Bounds;
OverlayWindow overlayWindow = new OverlayWindow();
overlayWindow.Top = bounds.Top;
overlayWindow.Left = bounds.Left;
overlayWindow.Width = bounds.Width;
overlayWindow.Height = bounds.Height;
overlayWindow.ShowActivated = true;
overlayWindow.Topmost = true;
overlayWindow.Show();
_overlayWindows.Add(overlayWindow);
}
_snapshotWindow = new SnapshotWindow(this);
_snapshotWindow.ShowActivated = true;
_snapshotWindow.Topmost = true;
_snapshotWindow.Show();
}
internal void CancelSnapshot()
{
foreach (OverlayWindow overlayWindow in _overlayWindows)
{
overlayWindow.Close();
}
_mainWindow.WindowState = System.Windows.WindowState.Normal;
}
2024-07-16 09:37:40 +02:00
internal void LaunchAndEdit(Project project)
{
LaunchEditedProject(project);
projectBeforeLaunch = new Project(project);
EnterSnapshotMode(true);
}
private void LaunchEditedProject(Project project)
{
_projectsEditorIO.SerializeTempProject(project);
RunLauncher(TempProjectData.File);
}
2024-05-21 16:55:15 +02:00
}
}