using System.Windows; using System.Windows.Controls; using System.Windows.Forms; using System.Windows.Media; using System.IO; using System.Text; using System.Windows.Input; using TaskbarLauncher.Models; using TaskbarLauncher.Services; using MessageBox = System.Windows.MessageBox; using TreeView = System.Windows.Controls.TreeView; using TreeViewItem = System.Windows.Controls.TreeViewItem; namespace TaskbarLauncher; public partial class MainWindow : Window { private readonly LauncherController? controller; private LauncherItem? selectedItem; private bool isLoadingSelection; private bool isLoadingStartupState; private List? selectedPath; private string? loadedAutoIconPath; private System.Windows.Point dragStartPoint; private sealed record TargetValidationIssue(string ItemPath, string Problem); public MainWindow() { InitializeComponent(); } public MainWindow(LauncherController controller) { InitializeComponent(); this.controller = controller; DisplayModeBox.ItemsSource = Enum.GetValues(); ItemDisplayModeBox.ItemsSource = Enum.GetValues(); ThemeSchemeBox.ItemsSource = Enum.GetValues(); TypeBox.ItemsSource = Enum.GetValues(); VersionText.Text = $"Version {AppInfo.Version} Build {AppInfo.Build}"; RefreshView(); } public void SelectItemForEdit(LauncherItem item) { if (controller is null) { return; } RefreshView(item); Show(); Activate(); } private void RefreshView(LauncherItem? itemToSelect = null) { if (controller is null) { return; } ItemsTree.Items.Clear(); foreach (LauncherItem item in controller.Config.Items) { ItemsTree.Items.Add(CreateTreeItem(item)); } HotkeyBox.Text = controller.Config.Hotkey; DisplayModeBox.SelectedItem = controller.Config.DisplayMode; ThemeSchemeBox.SelectedItem = controller.Config.ThemeScheme; LoadStartupState(); StatusText.Text = "Config file: config.json beside TaskbarLauncher.exe"; if (itemToSelect is not null && SelectTreeItem(itemToSelect)) { selectedItem = itemToSelect; selectedPath = FindItemPath(itemToSelect); LoadItemFields(itemToSelect); } else { ClearItemFields(); } } private TreeViewItem CreateTreeItem(LauncherItem item) { var pinMenuItem = new System.Windows.Controls.MenuItem { Header = item.IsFavorite ? "Unpin from Top" : "Pin to Top" }; pinMenuItem.Click += ToggleFavorite_Click; var disableMenuItem = new System.Windows.Controls.MenuItem { Header = item.IsDisabled ? "Enable Item" : "Disable Item" }; disableMenuItem.Click += ToggleDisabled_Click; var openContainingFolderMenuItem = new System.Windows.Controls.MenuItem { Header = "Open Containing Folder", IsEnabled = CanOpenContainingFolder(item) }; openContainingFolderMenuItem.Click += OpenContainingFolder_Click; var duplicateMenuItem = new System.Windows.Controls.MenuItem { Header = "Duplicate Item" }; duplicateMenuItem.Click += DuplicateItem_Click; var sortMenuItem = new System.Windows.Controls.MenuItem { Header = item.Type == LauncherItemType.Menu ? "Sort This Menu" : "Sort This Level" }; sortMenuItem.Click += SortSelected_Click; var treeItem = new TreeViewItem { Header = $"{GetTreeStatusPrefix(item)}{item.Title} ({item.Type})", Tag = item, ContextMenu = new System.Windows.Controls.ContextMenu() }; treeItem.ContextMenu.Items.Add(pinMenuItem); treeItem.ContextMenu.Items.Add(disableMenuItem); treeItem.ContextMenu.Items.Add(openContainingFolderMenuItem); treeItem.ContextMenu.Items.Add(duplicateMenuItem); treeItem.ContextMenu.Items.Add(sortMenuItem); treeItem.PreviewMouseRightButtonDown += (_, e) => { treeItem.IsSelected = true; e.Handled = false; }; foreach (LauncherItem child in item.Children) { treeItem.Items.Add(CreateTreeItem(child)); } return treeItem; } private static string GetTreeStatusPrefix(LauncherItem item) { List parts = []; if (item.IsFavorite) { parts.Add("Pinned"); } if (item.IsDisabled) { parts.Add("Disabled"); } return parts.Count == 0 ? "" : $"[{string.Join(", ", parts)}] "; } private bool SelectTreeItem(LauncherItem item) { foreach (object root in ItemsTree.Items) { if (root is TreeViewItem treeItem && SelectTreeItem(treeItem, item)) { return true; } } return false; } private static bool SelectTreeItem(TreeViewItem treeItem, LauncherItem item) { if (ReferenceEquals(treeItem.Tag, item)) { treeItem.IsSelected = true; treeItem.BringIntoView(); return true; } foreach (object child in treeItem.Items) { if (child is TreeViewItem childTreeItem && SelectTreeItem(childTreeItem, item)) { treeItem.IsExpanded = true; return true; } } return false; } private void ItemsTree_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs e) { if (!isLoadingSelection && selectedItem is not null) { ApplyCurrentItem(); } if (e.NewValue is not TreeViewItem treeItem || treeItem.Tag is not LauncherItem item) { selectedItem = null; ClearItemFields(); return; } selectedItem = item; selectedPath = FindItemPath(item); LoadItemFields(item); } private void ItemsTree_PreviewMouseMove(object sender, System.Windows.Input.MouseEventArgs e) { if (e.LeftButton != MouseButtonState.Pressed) { dragStartPoint = e.GetPosition(ItemsTree); return; } System.Windows.Point currentPosition = e.GetPosition(ItemsTree); if (Math.Abs(currentPosition.X - dragStartPoint.X) < SystemParameters.MinimumHorizontalDragDistance && Math.Abs(currentPosition.Y - dragStartPoint.Y) < SystemParameters.MinimumVerticalDragDistance) { return; } if (ItemsTree.SelectedItem is TreeViewItem { Tag: LauncherItem item }) { DragDrop.DoDragDrop(ItemsTree, item, System.Windows.DragDropEffects.Move); } } private void ItemsTree_DragOver(object sender, System.Windows.DragEventArgs e) { if (CanMoveDroppedItem(e)) { e.Effects = System.Windows.DragDropEffects.Move; } else if (CanAddExternalDroppedItems(e.Data)) { e.Effects = System.Windows.DragDropEffects.Copy; } else { e.Effects = System.Windows.DragDropEffects.None; } e.Handled = true; } private void ItemsTree_Drop(object sender, System.Windows.DragEventArgs e) { if (controller is null) { return; } if (e.Data.GetDataPresent(typeof(LauncherItem)) && e.Data.GetData(typeof(LauncherItem)) is LauncherItem draggedItem) { MoveDroppedItem(e, draggedItem); return; } AddExternalDroppedItems(e); } private void MoveDroppedItem(System.Windows.DragEventArgs e, LauncherItem draggedItem) { if (controller is null) { return; } TreeViewItem? targetTreeItem = FindAncestor(e.OriginalSource as DependencyObject); LauncherItem? targetItem = targetTreeItem?.Tag as LauncherItem; if (targetItem is not null && (ReferenceEquals(draggedItem, targetItem) || ContainsItem(draggedItem, targetItem))) { return; } ApplyCurrentItem(); List? sourceSiblings = FindSiblings(controller.Config.Items, draggedItem); if (sourceSiblings is null) { return; } sourceSiblings.Remove(draggedItem); if (targetItem is null) { controller.Config.Items.Add(draggedItem); } else if (targetItem.Type == LauncherItemType.Menu) { targetItem.Children.Add(draggedItem); } else { List? targetSiblings = FindSiblings(controller.Config.Items, targetItem); if (targetSiblings is null) { controller.Config.Items.Add(draggedItem); } else { int targetIndex = targetSiblings.IndexOf(targetItem); targetSiblings.Insert(targetIndex + 1, draggedItem); } } selectedPath = FindItemPath(draggedItem); RefreshView(draggedItem); StatusText.Text = $"Moved {draggedItem.Title}. Click Save to keep the new order."; e.Handled = true; } private void AddExternalDroppedItems(System.Windows.DragEventArgs e) { if (controller is null) { return; } List droppedItems = CreateItemsFromDropData(e.Data, controller.Config.DisplayMode); if (droppedItems.Count == 0) { return; } ApplyCurrentItem(); TreeViewItem? targetTreeItem = FindAncestor(e.OriginalSource as DependencyObject); LauncherItem? targetItem = targetTreeItem?.Tag as LauncherItem; if (targetItem is null) { controller.Config.Items.AddRange(droppedItems); } else if (targetItem.Type == LauncherItemType.Menu) { targetItem.Children.AddRange(droppedItems); if (targetTreeItem is not null) { targetTreeItem.IsExpanded = true; } } else { List? targetSiblings = FindSiblings(controller.Config.Items, targetItem); if (targetSiblings is null) { controller.Config.Items.AddRange(droppedItems); } else { int targetIndex = targetSiblings.IndexOf(targetItem); targetSiblings.InsertRange(targetIndex + 1, droppedItems); } } LauncherItem itemToSelect = droppedItems[0]; selectedPath = FindItemPath(itemToSelect); RefreshView(itemToSelect); StatusText.Text = droppedItems.Count == 1 ? $"Added {itemToSelect.Title}. Click Save to keep it." : $"Added {droppedItems.Count} items. Click Save to keep them."; e.Handled = true; } private bool CanMoveDroppedItem(System.Windows.DragEventArgs e) { if (!e.Data.GetDataPresent(typeof(LauncherItem)) || e.Data.GetData(typeof(LauncherItem)) is not LauncherItem draggedItem) { return false; } TreeViewItem? targetTreeItem = FindAncestor(e.OriginalSource as DependencyObject); LauncherItem? targetItem = targetTreeItem?.Tag as LauncherItem; return targetItem is null || (!ReferenceEquals(draggedItem, targetItem) && !ContainsItem(draggedItem, targetItem)); } private static bool CanAddExternalDroppedItems(System.Windows.IDataObject data) { return data.GetDataPresent(System.Windows.DataFormats.FileDrop) || data.GetDataPresent(System.Windows.DataFormats.UnicodeText) || data.GetDataPresent(System.Windows.DataFormats.Text) || data.GetDataPresent("UniformResourceLocatorW") || data.GetDataPresent("UniformResourceLocator"); } private static List CreateItemsFromDropData(System.Windows.IDataObject data, LauncherDisplayMode displayMode) { List items = []; if (data.GetDataPresent(System.Windows.DataFormats.FileDrop) && data.GetData(System.Windows.DataFormats.FileDrop) is string[] paths) { foreach (string path in paths) { LauncherItem? item = CreateItemFromPath(path, displayMode); if (item is not null) { items.Add(item); } } } if (items.Count > 0) { return items; } string? droppedText = GetDroppedText(data); if (!string.IsNullOrWhiteSpace(droppedText)) { foreach (string line in droppedText.Split(["\r\n", "\n"], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) { LauncherItem? item = CreateItemFromText(line, displayMode); if (item is not null) { items.Add(item); } } } return items; } private static string? GetDroppedText(System.Windows.IDataObject data) { if (data.GetDataPresent(System.Windows.DataFormats.UnicodeText)) { return data.GetData(System.Windows.DataFormats.UnicodeText) as string; } if (data.GetDataPresent(System.Windows.DataFormats.Text)) { return data.GetData(System.Windows.DataFormats.Text) as string; } return GetDroppedUrlFormatText(data, "UniformResourceLocatorW", Encoding.Unicode) ?? GetDroppedUrlFormatText(data, "UniformResourceLocator", Encoding.UTF8); } private static string? GetDroppedUrlFormatText(System.Windows.IDataObject data, string format, Encoding encoding) { if (!data.GetDataPresent(format)) { return null; } object? rawData = data.GetData(format); return rawData switch { string text => text, byte[] bytes => DecodeDroppedUrlBytes(bytes, encoding), MemoryStream stream => DecodeDroppedUrlBytes(stream.ToArray(), encoding), _ => null }; } private static string DecodeDroppedUrlBytes(byte[] bytes, Encoding encoding) { return encoding.GetString(bytes).TrimEnd('\0', '\r', '\n', ' '); } private static LauncherItem? CreateItemFromText(string text, LauncherDisplayMode displayMode) { string target = text.Trim(); if (!Uri.TryCreate(target, UriKind.Absolute, out Uri? uri) || (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps)) { return null; } return new LauncherItem { Title = GetWebsiteTitle(uri), Type = LauncherItemType.Website, Target = uri.ToString(), IconPath = uri.ToString(), DisplayMode = displayMode }; } private static LauncherItem CreateWebsiteItem(Uri uri, string title, LauncherDisplayMode displayMode) { return new LauncherItem { Title = title, Type = LauncherItemType.Website, Target = uri.ToString(), IconPath = uri.ToString(), DisplayMode = displayMode }; } private static LauncherItem? CreateItemFromPath(string path, LauncherDisplayMode displayMode) { if (string.IsNullOrWhiteSpace(path)) { return null; } string fullPath = path.Trim(); if (Directory.Exists(fullPath)) { return new LauncherItem { Title = new DirectoryInfo(fullPath).Name, Type = LauncherItemType.Folder, Target = fullPath, IconPath = fullPath, DisplayMode = displayMode }; } if (!File.Exists(fullPath)) { return null; } if (Path.GetExtension(fullPath).Equals(".url", StringComparison.OrdinalIgnoreCase)) { return CreateItemFromInternetShortcut(fullPath, displayMode); } LauncherItemType type = IsApplicationPath(fullPath) ? LauncherItemType.App : LauncherItemType.File; return new LauncherItem { Title = Path.GetFileNameWithoutExtension(fullPath), Type = type, Target = fullPath, IconPath = fullPath, DisplayMode = displayMode }; } private static LauncherItem? CreateItemFromInternetShortcut(string path, LauncherDisplayMode displayMode) { foreach (string line in File.ReadLines(path)) { if (!line.StartsWith("URL=", StringComparison.OrdinalIgnoreCase)) { continue; } string target = line["URL=".Length..].Trim(); if (Uri.TryCreate(target, UriKind.Absolute, out Uri? uri) && (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)) { return CreateWebsiteItem(uri, Path.GetFileNameWithoutExtension(path), displayMode); } } return null; } private static bool IsApplicationPath(string path) { string extension = Path.GetExtension(path); return extension.Equals(".exe", StringComparison.OrdinalIgnoreCase) || extension.Equals(".lnk", StringComparison.OrdinalIgnoreCase) || extension.Equals(".bat", StringComparison.OrdinalIgnoreCase) || extension.Equals(".cmd", StringComparison.OrdinalIgnoreCase) || extension.Equals(".ps1", StringComparison.OrdinalIgnoreCase); } private static string GetWebsiteTitle(Uri uri) { string host = uri.Host; if (host.StartsWith("www.", StringComparison.OrdinalIgnoreCase)) { host = host[4..]; } return string.IsNullOrWhiteSpace(host) ? uri.ToString() : host; } private static bool ContainsItem(LauncherItem parent, LauncherItem possibleChild) { foreach (LauncherItem child in parent.Children) { if (ReferenceEquals(child, possibleChild) || ContainsItem(child, possibleChild)) { return true; } } return false; } private static T? FindAncestor(DependencyObject? current) where T : DependencyObject { while (current is not null) { if (current is T match) { return match; } current = VisualTreeHelper.GetParent(current); } return null; } private void LoadItemFields(LauncherItem item) { isLoadingSelection = true; TitleBox.Text = item.Title; TypeBox.SelectedItem = item.Type; TargetBox.Text = item.Target; ArgumentsBox.Text = item.Arguments ?? ""; WorkingFolderBox.Text = item.WorkingFolder ?? ""; NotesBox.Text = item.Notes ?? ""; IconPathBox.Text = item.IconPath ?? ""; loadedAutoIconPath = GetAutomaticIconPath(item); ItemDisplayModeBox.SelectedItem = item.DisplayMode; RunAsAdminBox.IsChecked = item.RunAsAdmin; StartMinimizedBox.IsChecked = item.StartMinimized; UpdateTargetAvailability(); isLoadingSelection = false; UpdatePreview(); } private void ClearItemFields() { selectedItem = null; selectedPath = null; isLoadingSelection = true; TitleBox.Text = ""; TypeBox.SelectedIndex = -1; TargetBox.Text = ""; ArgumentsBox.Text = ""; WorkingFolderBox.Text = ""; NotesBox.Text = ""; IconPathBox.Text = ""; loadedAutoIconPath = null; ItemDisplayModeBox.SelectedItem = controller?.Config.DisplayMode; RunAsAdminBox.IsChecked = false; StartMinimizedBox.IsChecked = false; UpdateTargetAvailability(); isLoadingSelection = false; UpdatePreview(); } private void ApplyCurrentItem() { if (controller is null || selectedItem is null) { return; } selectedItem.Title = string.IsNullOrWhiteSpace(TitleBox.Text) ? "Untitled" : TitleBox.Text.Trim(); selectedItem.Type = TypeBox.SelectedItem is LauncherItemType type ? type : LauncherItemType.App; selectedItem.Target = selectedItem.Type == LauncherItemType.Menu ? "" : TargetBox.Text.Trim(); selectedItem.Arguments = selectedItem.Type == LauncherItemType.Menu || string.IsNullOrWhiteSpace(ArgumentsBox.Text) ? null : ArgumentsBox.Text.Trim(); selectedItem.WorkingFolder = LauncherController.CanUseLaunchOptions(selectedItem.Type) && !string.IsNullOrWhiteSpace(WorkingFolderBox.Text) ? WorkingFolderBox.Text.Trim() : null; selectedItem.Notes = string.IsNullOrWhiteSpace(NotesBox.Text) ? null : NotesBox.Text.Trim(); EnsureAutomaticIconPath(); selectedItem.IconPath = string.IsNullOrWhiteSpace(IconPathBox.Text) ? null : IconPathBox.Text.Trim(); if (ItemDisplayModeBox.SelectedItem is LauncherDisplayMode itemMode) { selectedItem.DisplayMode = itemMode; } selectedItem.RunAsAdmin = LauncherController.CanRunAsAdmin(selectedItem.Type) && RunAsAdminBox.IsChecked == true; selectedItem.StartMinimized = LauncherController.CanUseLaunchOptions(selectedItem.Type) && StartMinimizedBox.IsChecked == true; if (selectedItem.Type != LauncherItemType.Menu) { selectedItem.Children.Clear(); } } private void ApplyConfigFields() { if (controller is null) { return; } controller.Config.Hotkey = string.IsNullOrWhiteSpace(HotkeyBox.Text) ? "Ctrl+Alt+Space" : HotkeyService.Normalize(HotkeyBox.Text.Trim()); controller.Config.DisplayMode = DisplayModeBox.SelectedItem is LauncherDisplayMode mode ? mode : LauncherDisplayMode.CompactList; controller.Config.ThemeScheme = ThemeSchemeBox.SelectedItem is LauncherThemeScheme scheme ? scheme : LauncherThemeScheme.MarkHaven; ApplyCurrentItem(); } private void HotkeyBox_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e) { Key key = e.Key == Key.System ? e.SystemKey : e.Key; if (key == Key.ImeProcessed) { key = e.ImeProcessedKey; } if (key is Key.LeftCtrl or Key.RightCtrl or Key.LeftAlt or Key.RightAlt or Key.LeftShift or Key.RightShift or Key.LWin or Key.RWin) { e.Handled = true; return; } if (key is Key.Back or Key.Delete or Key.Escape) { HotkeyBox.Text = ""; e.Handled = true; return; } ModifierKeys modifiers = Keyboard.Modifiers; if (modifiers == ModifierKeys.None) { StatusText.Text = "Press a hotkey with Ctrl, Alt, Shift, or Win plus another key."; e.Handled = true; return; } HotkeyBox.Text = FormatHotkey(modifiers, key); HotkeyBox.CaretIndex = HotkeyBox.Text.Length; StatusText.Text = "Click Save to apply the new hotkey."; e.Handled = true; } private static string FormatHotkey(ModifierKeys modifiers, Key key) { List parts = []; if (modifiers.HasFlag(ModifierKeys.Control)) { parts.Add("Ctrl"); } if (modifiers.HasFlag(ModifierKeys.Alt)) { parts.Add("Alt"); } if (modifiers.HasFlag(ModifierKeys.Shift)) { parts.Add("Shift"); } if (modifiers.HasFlag(ModifierKeys.Windows)) { parts.Add("Win"); } parts.Add(key.ToString()); return string.Join("+", parts); } private void LoadStartupState() { isLoadingStartupState = true; StartWithWindowsBox.IsChecked = StartupService.IsEnabled(); isLoadingStartupState = false; } private void StartWithWindowsBox_Changed(object sender, RoutedEventArgs e) { if (isLoadingStartupState) { return; } try { bool enabled = StartWithWindowsBox.IsChecked == true; StartupService.SetEnabled(enabled); StatusText.Text = enabled ? $"Startup enabled: {StartupService.ShortcutPath}" : "Startup disabled."; } catch (Exception ex) { MessageBox.Show($"Could not update startup setting.\n\n{ex.Message}", "Taskbar Launcher"); LoadStartupState(); } } private void AddRoot_Click(object sender, RoutedEventArgs e) { if (controller is null) { return; } ApplyCurrentItem(); LauncherItem item = CreateNewItem(controller.Config.DisplayMode); controller.Config.Items.Add(item); selectedPath = [controller.Config.Items.Count - 1]; RefreshView(item); } private void AddTopMenu_Click(object sender, RoutedEventArgs e) { if (controller is null) { return; } ApplyCurrentItem(); LauncherItem menu = CreateNewMenu(controller.Config.DisplayMode); controller.Config.Items.Add(menu); selectedPath = [controller.Config.Items.Count - 1]; RefreshView(menu); StatusText.Text = "Added top-level menu. Rename it, then Save."; } private void AddChild_Click(object sender, RoutedEventArgs e) { if (controller is null || selectedItem is null) { MessageBox.Show("Select a menu item first, then add a child item.", "Taskbar Launcher"); return; } LauncherItem parent = selectedItem; ApplyCurrentItem(); if (parent.Type != LauncherItemType.Menu) { MessageBox.Show("Child items can only be added under a Menu item.", "Taskbar Launcher"); return; } LauncherItem child = CreateNewItem(controller.Config.DisplayMode); parent.Children.Add(child); selectedPath = FindItemPath(child); RefreshView(child); } private void AddSubmenu_Click(object sender, RoutedEventArgs e) { if (controller is null || selectedItem is null) { MessageBox.Show("Select a menu first, then add a submenu.", "Taskbar Launcher"); return; } LauncherItem parent = selectedItem; ApplyCurrentItem(); if (parent.Type != LauncherItemType.Menu) { MessageBox.Show("Submenus can only be added under a Menu item.", "Taskbar Launcher"); return; } LauncherItem submenu = CreateNewMenu(controller.Config.DisplayMode); parent.Children.Add(submenu); selectedPath = FindItemPath(submenu); RefreshView(submenu); StatusText.Text = "Added submenu. Rename it, then Save."; } private void ToggleFavorite_Click(object sender, RoutedEventArgs e) { if (controller is null || selectedItem is null) { return; } ApplyCurrentItem(); selectedItem.IsFavorite = !selectedItem.IsFavorite; LauncherItem itemToSelect = selectedItem; selectedPath = FindItemPath(itemToSelect); RefreshView(itemToSelect); StatusText.Text = itemToSelect.IsFavorite ? $"Pinned {itemToSelect.Title}. Click Save to keep it." : $"Unpinned {itemToSelect.Title}. Click Save to keep it."; } private void ToggleDisabled_Click(object sender, RoutedEventArgs e) { if (controller is null || selectedItem is null) { return; } ApplyCurrentItem(); selectedItem.IsDisabled = !selectedItem.IsDisabled; LauncherItem itemToSelect = selectedItem; selectedPath = FindItemPath(itemToSelect); RefreshView(itemToSelect); StatusText.Text = itemToSelect.IsDisabled ? $"Disabled {itemToSelect.Title}. Click Save to keep it." : $"Enabled {itemToSelect.Title}. Click Save to keep it."; } private void OpenContainingFolder_Click(object sender, RoutedEventArgs e) { if (selectedItem is null) { return; } ApplyCurrentItem(); if (!LauncherController.OpenContainingFolder(selectedItem, out string? error) && !string.IsNullOrWhiteSpace(error)) { MessageBox.Show(error, "Taskbar Launcher"); } } private static bool CanOpenContainingFolder(LauncherItem item) { return item.Type is LauncherItemType.App or LauncherItemType.File && !string.IsNullOrWhiteSpace(item.Target); } private void DuplicateItem_Click(object sender, RoutedEventArgs e) { if (controller is null || selectedItem is null) { return; } ApplyCurrentItem(); string originalTitle = selectedItem.Title; LauncherItem duplicate = DuplicateItem(selectedItem); List? siblings = FindSiblings(controller.Config.Items, selectedItem); if (siblings is null) { controller.Config.Items.Add(duplicate); } else { int selectedIndex = siblings.IndexOf(selectedItem); siblings.Insert(selectedIndex + 1, duplicate); } selectedPath = FindItemPath(duplicate); RefreshView(duplicate); StatusText.Text = $"Duplicated {originalTitle}. Click Save to keep it."; } private static LauncherItem CreateNewItem(LauncherDisplayMode displayMode) { return new LauncherItem { Title = "New Item", Type = LauncherItemType.App, Target = "", DisplayMode = displayMode }; } private static LauncherItem CreateNewMenu(LauncherDisplayMode displayMode) { return new LauncherItem { Title = "New Menu", Type = LauncherItemType.Menu, Target = "", DisplayMode = displayMode }; } private static LauncherItem DuplicateItem(LauncherItem item) { return DuplicateItem(item, renameRoot: true); } private static LauncherItem DuplicateItem(LauncherItem item, bool renameRoot) { return new LauncherItem { Title = renameRoot ? GetDuplicateTitle(item.Title) : item.Title, Type = item.Type, Target = item.Target, Arguments = item.Arguments, WorkingFolder = item.WorkingFolder, Notes = item.Notes, IconPath = item.IconPath, IsFavorite = item.IsFavorite, IsDisabled = item.IsDisabled, RunAsAdmin = item.RunAsAdmin, StartMinimized = item.StartMinimized, DisplayMode = item.DisplayMode, Children = item.Children.Select(child => DuplicateItem(child, renameRoot: false)).ToList() }; } private static string GetDuplicateTitle(string title) { return string.IsNullOrWhiteSpace(title) ? "Copy" : $"{title} Copy"; } private void Delete_Click(object sender, RoutedEventArgs e) { if (controller is null || selectedItem is null) { return; } LauncherItem deletedItem = selectedItem; RemoveItem(controller.Config.Items, selectedItem); RefreshView(); StatusText.Text = $"Deleted: {deletedItem.Title}"; } private void MoveUp_Click(object sender, RoutedEventArgs e) { MoveSelected(-1); } private void MoveDown_Click(object sender, RoutedEventArgs e) { MoveSelected(1); } private void PromoteToTop_Click(object sender, RoutedEventArgs e) { if (controller is null || selectedItem is null) { return; } ApplyCurrentItem(); if (controller.Config.Items.Contains(selectedItem)) { StatusText.Text = $"{selectedItem.Title} is already a top-level item."; return; } LauncherItem itemToPromote = selectedItem; if (!RemoveItem(controller.Config.Items, itemToPromote)) { return; } controller.Config.Items.Add(itemToPromote); selectedPath = [controller.Config.Items.Count - 1]; RefreshView(itemToPromote); StatusText.Text = $"Promoted {itemToPromote.Title} to top level. Click Save to keep it."; } private void MoveSelected(int direction) { if (controller is null || selectedItem is null) { return; } ApplyCurrentItem(); List? siblings = FindSiblings(controller.Config.Items, selectedItem); if (siblings is null) { return; } int index = siblings.IndexOf(selectedItem); int newIndex = index + direction; if (index < 0 || newIndex < 0 || newIndex >= siblings.Count) { return; } siblings.RemoveAt(index); siblings.Insert(newIndex, selectedItem); RefreshView(selectedItem); } private void SortSelected_Click(object sender, RoutedEventArgs e) { if (controller is null || selectedItem is null) { return; } ApplyCurrentItem(); LauncherItem itemToSelect = selectedItem; if (selectedItem.Type == LauncherItemType.Menu) { SortItems(selectedItem.Children); RefreshView(itemToSelect); StatusText.Text = $"Sorted {itemToSelect.Title}. Click Save to keep it."; return; } List? siblings = FindSiblings(controller.Config.Items, selectedItem); if (siblings is null) { return; } SortItems(siblings); RefreshView(itemToSelect); StatusText.Text = $"Sorted this level. Click Save to keep it."; } private static void SortItems(List items) { List sortedItems = items .OrderByDescending(item => item.IsFavorite) .ThenBy(item => item.Title, StringComparer.CurrentCultureIgnoreCase) .ThenBy(item => item.Type) .ToList(); items.Clear(); items.AddRange(sortedItems); } private static bool RemoveItem(List items, LauncherItem item) { if (items.Remove(item)) { return true; } foreach (LauncherItem child in items) { if (RemoveItem(child.Children, item)) { return true; } } return false; } private static List? FindSiblings(List items, LauncherItem item) { if (items.Contains(item)) { return items; } foreach (LauncherItem child in items) { List? found = FindSiblings(child.Children, item); if (found is not null) { return found; } } return null; } private void ApplyItem_Click(object sender, RoutedEventArgs e) { ApplyCurrentItem(); RefreshView(selectedItem); } private void Save_Click(object sender, RoutedEventArgs e) { if (controller is null) { return; } string previousHotkey = controller.Config.Hotkey; try { ApplyConfigFields(); ConfigService.Save(controller.Config); controller.ReloadConfig(); } catch (Exception ex) when (ex is FormatException or InvalidOperationException) { controller.Config.Hotkey = previousHotkey; ConfigService.Save(controller.Config); controller.ReloadConfig(); HotkeyBox.Text = previousHotkey; MessageBox.Show($"Could not apply hotkey.\n\n{ex.Message}", "Taskbar Launcher"); StatusText.Text = $"Hotkey was not changed. Current hotkey: {previousHotkey}"; return; } LauncherItem? reloadedSelection = selectedPath is null ? null : FindItemByPath(controller.Config.Items, selectedPath); RefreshView(reloadedSelection); StatusText.Text = reloadedSelection is null ? $"Saved: {ConfigService.ConfigPath}" : $"Saved {reloadedSelection.Title} ({reloadedSelection.Type}) to {ConfigService.ConfigPath}"; } private void BackupConfig_Click(object sender, RoutedEventArgs e) { try { if (controller is not null) { ApplyConfigFields(); ConfigService.Save(controller.Config); } string backupPath = ConfigService.Backup(); StatusText.Text = $"Backed up config to {backupPath}"; } catch (Exception ex) { MessageBox.Show($"Could not back up config.\n\n{ex.Message}", "Taskbar Launcher"); } } private void RestoreConfig_Click(object sender, RoutedEventArgs e) { using OpenFileDialog dialog = new() { Title = "Choose a config backup to restore", Filter = "JSON config files|*.json|All files|*.*", InitialDirectory = Directory.Exists(ConfigService.BackupFolder) ? ConfigService.BackupFolder : ConfigService.AppFolder }; if (dialog.ShowDialog() != System.Windows.Forms.DialogResult.OK) { return; } MessageBoxResult result = MessageBox.Show( "Restore this config file and replace the current launcher config?", "Taskbar Launcher", MessageBoxButton.YesNo, MessageBoxImage.Question); if (result != MessageBoxResult.Yes) { return; } try { ConfigService.Restore(dialog.FileName); controller?.ReloadConfig(); RefreshView(); StatusText.Text = $"Restored config from {dialog.FileName}"; } catch (Exception ex) { MessageBox.Show($"Could not restore config.\n\n{ex.Message}", "Taskbar Launcher"); } } private void ConfigMenuButton_Click(object sender, RoutedEventArgs e) { if (ConfigMenuButton.ContextMenu is null) { return; } ConfigMenuButton.ContextMenu.PlacementTarget = ConfigMenuButton; ConfigMenuButton.ContextMenu.IsOpen = true; } private void ExportConfig_Click(object sender, RoutedEventArgs e) { if (controller is null) { return; } ApplyConfigFields(); using SaveFileDialog dialog = new() { Title = "Export launcher config", Filter = "JSON config files|*.json|All files|*.*", FileName = $"TaskbarLauncher-config-{DateTime.Now:yyyyMMdd-HHmmss}.json", InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) }; if (dialog.ShowDialog() != System.Windows.Forms.DialogResult.OK) { return; } try { string exportPath = ConfigService.ExportTo(dialog.FileName, controller.Config); StatusText.Text = $"Exported config to {exportPath}"; } catch (Exception ex) { MessageBox.Show($"Could not export config.\n\n{ex.Message}", "Taskbar Launcher"); } } private void ImportConfig_Click(object sender, RoutedEventArgs e) { using OpenFileDialog dialog = new() { Title = "Import launcher config", Filter = "JSON config files|*.json|All files|*.*", InitialDirectory = Directory.Exists(ConfigService.BackupFolder) ? ConfigService.BackupFolder : ConfigService.AppFolder }; if (dialog.ShowDialog() != System.Windows.Forms.DialogResult.OK) { return; } MessageBoxResult result = MessageBox.Show( "Import this config file and replace the current launcher config?\n\nA local backup will be created first.", "Taskbar Launcher", MessageBoxButton.YesNo, MessageBoxImage.Question); if (result != MessageBoxResult.Yes) { return; } try { string backupPath = ConfigService.Backup(); ConfigService.ImportFrom(dialog.FileName); controller?.ReloadConfig(); RefreshView(); StatusText.Text = $"Imported config from {dialog.FileName}. Previous config backed up to {backupPath}"; } catch (Exception ex) { MessageBox.Show($"Could not import config.\n\n{ex.Message}", "Taskbar Launcher"); } } private void ExportSelectedItem_Click(object sender, RoutedEventArgs e) { if (selectedItem is null) { MessageBox.Show("Select an item or menu to export first.", "Taskbar Launcher"); return; } ApplyCurrentItem(); string safeTitle = MakeSafeFileName(string.IsNullOrWhiteSpace(selectedItem.Title) ? "launcher-item" : selectedItem.Title); using SaveFileDialog dialog = new() { Title = "Export selected launcher item", Filter = "JSON item files|*.json|All files|*.*", FileName = $"{safeTitle}-{DateTime.Now:yyyyMMdd-HHmmss}.json", InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) }; if (dialog.ShowDialog() != System.Windows.Forms.DialogResult.OK) { return; } try { string exportPath = ConfigService.ExportItemTo(dialog.FileName, selectedItem); StatusText.Text = $"Exported {selectedItem.Title} to {exportPath}"; } catch (Exception ex) { MessageBox.Show($"Could not export selected item.\n\n{ex.Message}", "Taskbar Launcher"); } } private void ImportItem_Click(object sender, RoutedEventArgs e) { if (controller is null) { return; } using OpenFileDialog dialog = new() { Title = "Import launcher item", Filter = "JSON item files|*.json|All files|*.*", InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) }; if (dialog.ShowDialog() != System.Windows.Forms.DialogResult.OK) { return; } try { ApplyCurrentItem(); LauncherItem importedItem = ConfigService.ImportItemFrom(dialog.FileName); AddImportedItem(importedItem); selectedPath = FindItemPath(importedItem); RefreshView(importedItem); StatusText.Text = $"Imported {importedItem.Title}. Click Save to keep it."; } catch (Exception ex) { MessageBox.Show($"Could not import item.\n\n{ex.Message}", "Taskbar Launcher"); } } private void AddImportedItem(LauncherItem importedItem) { if (controller is null) { return; } if (selectedItem is null) { controller.Config.Items.Add(importedItem); return; } if (selectedItem.Type == LauncherItemType.Menu) { selectedItem.Children.Add(importedItem); return; } List? siblings = FindSiblings(controller.Config.Items, selectedItem); if (siblings is null) { controller.Config.Items.Add(importedItem); return; } int selectedIndex = siblings.IndexOf(selectedItem); siblings.Insert(selectedIndex + 1, importedItem); } private static string MakeSafeFileName(string fileName) { foreach (char invalidChar in Path.GetInvalidFileNameChars()) { fileName = fileName.Replace(invalidChar, '-'); } return fileName.Trim(); } private void ResetConfig_Click(object sender, RoutedEventArgs e) { MessageBoxResult result = MessageBox.Show( "Reset launcher config to the default folders and apps?\n\nA local backup will be created first.", "Taskbar Launcher", MessageBoxButton.YesNo, MessageBoxImage.Warning); if (result != MessageBoxResult.Yes) { return; } try { string backupPath = ConfigService.Backup(); ConfigService.ResetToDefaults(); controller?.ReloadConfig(); RefreshView(); StatusText.Text = $"Reset config to defaults. Previous config backed up to {backupPath}"; } catch (Exception ex) { MessageBox.Show($"Could not reset config.\n\n{ex.Message}", "Taskbar Launcher"); } } private List? FindItemPath(LauncherItem item) { if (controller is null) { return null; } List path = []; return FindItemPath(controller.Config.Items, item, path) ? path : null; } private static bool FindItemPath(List items, LauncherItem item, List path) { for (int index = 0; index < items.Count; index++) { path.Add(index); if (ReferenceEquals(items[index], item) || FindItemPath(items[index].Children, item, path)) { return true; } path.RemoveAt(path.Count - 1); } return false; } private static LauncherItem? FindItemByPath(List items, List path) { List currentItems = items; LauncherItem? currentItem = null; foreach (int index in path) { if (index < 0 || index >= currentItems.Count) { return null; } currentItem = currentItems[index]; currentItems = currentItem.Children; } return currentItem; } private void ReloadConfig_Click(object sender, RoutedEventArgs e) { controller?.ReloadConfig(); RefreshView(); } private void OpenConfigFolder_Click(object sender, RoutedEventArgs e) { ConfigService.OpenConfigFolder(); } private void ValidateTargets_Click(object sender, RoutedEventArgs e) { if (controller is null) { return; } ApplyConfigFields(); List issues = []; ValidateTargets(controller.Config.Items, [], issues); if (issues.Count == 0) { StatusText.Text = "Validation complete. No broken targets found."; MessageBox.Show("No broken targets found.", "Taskbar Launcher"); return; } string message = $"Found {issues.Count} possible broken target(s):\n\n" + string.Join("\n", issues.Take(20).Select(issue => $"- {issue.ItemPath}: {issue.Problem}")); if (issues.Count > 20) { message += $"\n\nShowing first 20 of {issues.Count}."; } StatusText.Text = $"Validation found {issues.Count} possible broken target(s)."; MessageBox.Show(message, "Taskbar Launcher"); } private static void ValidateTargets(IEnumerable items, List parentNames, List issues) { foreach (LauncherItem item in items) { List itemPath = [.. parentNames, string.IsNullOrWhiteSpace(item.Title) ? "Untitled" : item.Title]; if (!item.IsDisabled && TryGetTargetIssue(item, out string? problem) && problem is not null) { issues.Add(new TargetValidationIssue(string.Join(" > ", itemPath), problem)); } if (!item.IsDisabled && TryGetWorkingFolderIssue(item, out string? workingFolderProblem) && workingFolderProblem is not null) { issues.Add(new TargetValidationIssue(string.Join(" > ", itemPath), workingFolderProblem)); } if (item.Children.Count > 0) { ValidateTargets(item.Children, itemPath, issues); } } } private static bool TryGetTargetIssue(LauncherItem item, out string? problem) { problem = null; if (item.Type == LauncherItemType.Menu || item.IsDisabled) { return false; } if (string.IsNullOrWhiteSpace(item.Target)) { problem = "Target is blank."; return true; } string target = Environment.ExpandEnvironmentVariables(item.Target.Trim()); switch (item.Type) { case LauncherItemType.Folder: if (!Directory.Exists(target)) { problem = "Folder does not exist."; return true; } return false; case LauncherItemType.Website: if (!Uri.TryCreate(target, UriKind.Absolute, out Uri? uri) || (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps)) { problem = "Website target is not a valid http or https URL."; return true; } return false; case LauncherItemType.App: if (!File.Exists(target) && !CommandExistsOnPath(target)) { problem = "Application or shortcut was not found."; return true; } return false; case LauncherItemType.File: if (!File.Exists(target)) { problem = "File does not exist."; return true; } return false; default: problem = "Item type is not recognized."; return true; } } private static bool TryGetWorkingFolderIssue(LauncherItem item, out string? problem) { problem = null; if (!LauncherController.CanUseLaunchOptions(item.Type) || string.IsNullOrWhiteSpace(item.WorkingFolder)) { return false; } string workingFolder = Environment.ExpandEnvironmentVariables(item.WorkingFolder.Trim()); if (Directory.Exists(workingFolder)) { return false; } problem = "Working folder does not exist."; return true; } private static bool CommandExistsOnPath(string target) { if (target.IndexOfAny([Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar]) >= 0) { return false; } string? pathValue = Environment.GetEnvironmentVariable("PATH"); if (string.IsNullOrWhiteSpace(pathValue)) { return false; } string extension = Path.GetExtension(target); IEnumerable candidateNames = string.IsNullOrWhiteSpace(extension) ? GetExecutableCandidateNames(target) : [target]; foreach (string directory in pathValue.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) { foreach (string candidateName in candidateNames) { string candidatePath = Path.Combine(directory, candidateName); if (File.Exists(candidatePath)) { return true; } } } return false; } private static IEnumerable GetExecutableCandidateNames(string target) { string? pathExtValue = Environment.GetEnvironmentVariable("PATHEXT"); string[] extensions = string.IsNullOrWhiteSpace(pathExtValue) ? [".EXE", ".CMD", ".BAT", ".COM"] : pathExtValue.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); foreach (string extension in extensions) { yield return target + extension; } } private void About_Click(object sender, RoutedEventArgs e) { AboutWindow aboutWindow = new() { Owner = this }; aboutWindow.ShowDialog(); } private void Close_Click(object sender, RoutedEventArgs e) { Hide(); } private void BrowseFile_Click(object sender, RoutedEventArgs e) { using OpenFileDialog dialog = new() { Title = "Choose an application, document, or shortcut", Filter = "Programs and shortcuts|*.exe;*.lnk;*.bat;*.cmd;*.ps1|All files|*.*" }; if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { TargetBox.Text = dialog.FileName; SetIconPathFromTargetIfAutomatic(dialog.FileName); } } private void BrowseFolder_Click(object sender, RoutedEventArgs e) { using FolderBrowserDialog dialog = new() { Description = "Choose a folder to launch" }; if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { TargetBox.Text = dialog.SelectedPath; TypeBox.SelectedItem = LauncherItemType.Folder; SetIconPathFromTargetIfAutomatic(dialog.SelectedPath); } } private void BrowseIcon_Click(object sender, RoutedEventArgs e) { using OpenFileDialog dialog = new() { Title = "Choose an icon file", Filter = "Icon files|*.ico;*.exe;*.dll|All files|*.*" }; if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { IconPathBox.Text = dialog.FileName; loadedAutoIconPath = null; } } private void ResetIcon_Click(object sender, RoutedEventArgs e) { if (TypeBox.SelectedItem is LauncherItemType.Menu) { IconPathBox.Text = ""; loadedAutoIconPath = null; UpdatePreview(); return; } string target = TargetBox.Text.Trim(); if (string.IsNullOrWhiteSpace(target)) { IconPathBox.Text = ""; loadedAutoIconPath = null; } else { IconPathBox.Text = target; loadedAutoIconPath = target; } UpdatePreview(); } private void TypeBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (!isLoadingSelection) { UpdateTargetAvailability(); UpdatePreview(); } } private void PreviewField_Changed(object sender, EventArgs e) { if (!isLoadingSelection) { UpdatePreview(); } } private void ItemDisplayModeBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (isLoadingSelection) { return; } if (selectedItem is not null && ItemDisplayModeBox.SelectedItem is LauncherDisplayMode itemMode) { selectedItem.DisplayMode = itemMode; selectedPath = FindItemPath(selectedItem); StatusText.Text = "Click Save to keep the selected item display mode."; } UpdatePreview(); } private void ThemeSchemeBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (controller is null || ThemeSchemeBox.SelectedItem is not LauncherThemeScheme scheme) { return; } ThemeService.Apply(scheme); } private void BrowseWorkingFolder_Click(object sender, RoutedEventArgs e) { using FolderBrowserDialog dialog = new() { Description = "Choose a working folder" }; if (!string.IsNullOrWhiteSpace(WorkingFolderBox.Text)) { string currentFolder = Environment.ExpandEnvironmentVariables(WorkingFolderBox.Text.Trim()); if (Directory.Exists(currentFolder)) { dialog.SelectedPath = currentFolder; } } if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { WorkingFolderBox.Text = dialog.SelectedPath; } } private void LaunchOptionBox_Changed(object sender, RoutedEventArgs e) { if (isLoadingSelection) { return; } UpdatePreview(); if (selectedItem is not null) { StatusText.Text = "Click Save to keep the selected item launch options."; } } private void EnsureAutomaticIconPath() { if (TypeBox.SelectedItem is LauncherItemType.Menu) { return; } string target = TargetBox.Text.Trim(); if (!string.IsNullOrWhiteSpace(target)) { SetIconPathFromTargetIfAutomatic(target); } } private void SetIconPathFromTargetIfAutomatic(string target) { string currentIconPath = IconPathBox.Text.Trim(); if (string.IsNullOrWhiteSpace(currentIconPath) || string.Equals(currentIconPath, loadedAutoIconPath, StringComparison.OrdinalIgnoreCase)) { IconPathBox.Text = target; loadedAutoIconPath = target; } } private static string? GetAutomaticIconPath(LauncherItem item) { if (item.Type == LauncherItemType.Menu || string.IsNullOrWhiteSpace(item.Target)) { return null; } return string.Equals(item.IconPath, item.Target, StringComparison.OrdinalIgnoreCase) ? item.Target : null; } private void UpdatePreview() { if (PreviewHost is null) { return; } LauncherItem? previewItem = CreatePreviewItem(); if (previewItem is null) { PreviewHost.Content = new TextBlock { Text = "Select an item to preview it.", Foreground = (System.Windows.Media.Brush)System.Windows.Application.Current.Resources["AppMutedTextBrush"] }; PreviewNote.Text = ""; return; } LauncherDisplayMode displayMode = previewItem.DisplayMode; PreviewHost.Content = CreatePreviewControl(previewItem, displayMode); PreviewNote.Text = previewItem.Type == LauncherItemType.Menu ? "Menu headers are shown as expandable section headers in the launcher. Display size applies to app, folder, file, and website items." : $"Icon {DisplayModeMetrics.GetIconSize(displayMode):0}px, row {DisplayModeMetrics.GetRowHeight(displayMode):0}px."; } private LauncherItem? CreatePreviewItem() { if (selectedItem is null) { return null; } return new LauncherItem { Title = string.IsNullOrWhiteSpace(TitleBox.Text) ? "Untitled" : TitleBox.Text.Trim(), Type = TypeBox.SelectedItem is LauncherItemType type ? type : LauncherItemType.App, Target = TargetBox.Text.Trim(), Arguments = string.IsNullOrWhiteSpace(ArgumentsBox.Text) ? null : ArgumentsBox.Text.Trim(), WorkingFolder = LauncherController.CanUseLaunchOptions(TypeBox.SelectedItem is LauncherItemType itemType ? itemType : LauncherItemType.App) && !string.IsNullOrWhiteSpace(WorkingFolderBox.Text) ? WorkingFolderBox.Text.Trim() : null, Notes = string.IsNullOrWhiteSpace(NotesBox.Text) ? null : NotesBox.Text.Trim(), IconPath = string.IsNullOrWhiteSpace(IconPathBox.Text) ? null : IconPathBox.Text.Trim(), IsFavorite = selectedItem.IsFavorite, IsDisabled = selectedItem.IsDisabled, RunAsAdmin = LauncherController.CanRunAsAdmin(TypeBox.SelectedItem is LauncherItemType adminType ? adminType : LauncherItemType.App) && RunAsAdminBox.IsChecked == true, StartMinimized = LauncherController.CanUseLaunchOptions(TypeBox.SelectedItem is LauncherItemType launchType ? launchType : LauncherItemType.App) && StartMinimizedBox.IsChecked == true, DisplayMode = ItemDisplayModeBox.SelectedItem is LauncherDisplayMode mode ? mode : selectedItem.DisplayMode }; } private static FrameworkElement CreatePreviewControl(LauncherItem item, LauncherDisplayMode displayMode) { if (item.Type == LauncherItemType.Menu) { return new TextBlock { Text = item.Title, FontWeight = FontWeights.SemiBold, VerticalAlignment = VerticalAlignment.Center }; } if (displayMode == LauncherDisplayMode.LargeIconOnly) { ImageSource? iconOnly = IconService.GetIcon(item, large: true); return iconOnly is null ? new TextBlock { Text = item.Title, HorizontalAlignment = System.Windows.HorizontalAlignment.Center } : new System.Windows.Controls.Image { Source = iconOnly, Width = DisplayModeMetrics.GetIconSize(displayMode), Height = DisplayModeMetrics.GetIconSize(displayMode), HorizontalAlignment = System.Windows.HorizontalAlignment.Center }; } StackPanel panel = new() { Orientation = System.Windows.Controls.Orientation.Horizontal }; bool large = displayMode == LauncherDisplayMode.LargeIconWithText; ImageSource? icon = IconService.GetIcon(item, large); if (icon is not null) { double size = DisplayModeMetrics.GetIconSize(displayMode); panel.Children.Add(new System.Windows.Controls.Image { Source = icon, Width = size, Height = size, Margin = new Thickness(0, 0, 10, 0), VerticalAlignment = VerticalAlignment.Center }); } panel.Children.Add(new TextBlock { Text = item.Title, FontSize = displayMode == LauncherDisplayMode.CompactList ? 12 : 14, VerticalAlignment = VerticalAlignment.Center, TextTrimming = TextTrimming.CharacterEllipsis }); return new Border { MinHeight = DisplayModeMetrics.GetRowHeight(displayMode), Child = panel }; } private void UpdateTargetAvailability() { bool isMenu = TypeBox.SelectedItem is LauncherItemType.Menu; bool canUseLaunchOptions = TypeBox.SelectedItem is LauncherItemType launchType && LauncherController.CanUseLaunchOptions(launchType); bool canRunAsAdmin = TypeBox.SelectedItem is LauncherItemType itemType && LauncherController.CanRunAsAdmin(itemType); TargetBox.IsEnabled = !isMenu; ArgumentsBox.IsEnabled = !isMenu; WorkingFolderBox.IsEnabled = canUseLaunchOptions; WorkingFolderBrowseButton.IsEnabled = canUseLaunchOptions; RunAsAdminBox.IsEnabled = canRunAsAdmin; StartMinimizedBox.IsEnabled = canUseLaunchOptions; if (isMenu && !isLoadingSelection) { TargetBox.Text = ""; ArgumentsBox.Text = ""; WorkingFolderBox.Text = ""; } if (!canRunAsAdmin && !isLoadingSelection) { RunAsAdminBox.IsChecked = false; } if (!canUseLaunchOptions && !isLoadingSelection) { WorkingFolderBox.Text = ""; StartMinimizedBox.IsChecked = false; } } protected override void OnClosing(System.ComponentModel.CancelEventArgs e) { e.Cancel = true; Hide(); } }