Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
691cb405fd | ||
|
|
992ac9a7b5 |
@@ -69,6 +69,8 @@
|
|||||||
<MenuItem Header="Reset to Defaults" Click="ResetConfig_Click"/>
|
<MenuItem Header="Reset to Defaults" Click="ResetConfig_Click"/>
|
||||||
<MenuItem Header="Reload Config" Click="ReloadConfig_Click"/>
|
<MenuItem Header="Reload Config" Click="ReloadConfig_Click"/>
|
||||||
<MenuItem Header="Open Config Folder" Click="OpenConfigFolder_Click"/>
|
<MenuItem Header="Open Config Folder" Click="OpenConfigFolder_Click"/>
|
||||||
|
<Separator/>
|
||||||
|
<MenuItem Header="Validate Targets" Click="ValidateTargets_Click"/>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
</Button.ContextMenu>
|
</Button.ContextMenu>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ public partial class MainWindow : Window
|
|||||||
private List<int>? selectedPath;
|
private List<int>? selectedPath;
|
||||||
private string? loadedAutoIconPath;
|
private string? loadedAutoIconPath;
|
||||||
private System.Windows.Point dragStartPoint;
|
private System.Windows.Point dragStartPoint;
|
||||||
|
private sealed record TargetValidationIssue(string ItemPath, string Problem);
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
@@ -96,6 +97,13 @@ public partial class MainWindow : Window
|
|||||||
};
|
};
|
||||||
disableMenuItem.Click += ToggleDisabled_Click;
|
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" };
|
var duplicateMenuItem = new System.Windows.Controls.MenuItem { Header = "Duplicate Item" };
|
||||||
duplicateMenuItem.Click += DuplicateItem_Click;
|
duplicateMenuItem.Click += DuplicateItem_Click;
|
||||||
|
|
||||||
@@ -107,6 +115,7 @@ public partial class MainWindow : Window
|
|||||||
};
|
};
|
||||||
treeItem.ContextMenu.Items.Add(pinMenuItem);
|
treeItem.ContextMenu.Items.Add(pinMenuItem);
|
||||||
treeItem.ContextMenu.Items.Add(disableMenuItem);
|
treeItem.ContextMenu.Items.Add(disableMenuItem);
|
||||||
|
treeItem.ContextMenu.Items.Add(openContainingFolderMenuItem);
|
||||||
treeItem.ContextMenu.Items.Add(duplicateMenuItem);
|
treeItem.ContextMenu.Items.Add(duplicateMenuItem);
|
||||||
treeItem.PreviewMouseRightButtonDown += (_, e) =>
|
treeItem.PreviewMouseRightButtonDown += (_, e) =>
|
||||||
{
|
{
|
||||||
@@ -867,6 +876,25 @@ public partial class MainWindow : Window
|
|||||||
: $"Enabled {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)
|
private void DuplicateItem_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (controller is null || selectedItem is null)
|
if (controller is null || selectedItem is null)
|
||||||
@@ -1316,6 +1344,159 @@ public partial class MainWindow : Window
|
|||||||
ConfigService.OpenConfigFolder();
|
ConfigService.OpenConfigFolder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ValidateTargets_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (controller is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyConfigFields();
|
||||||
|
List<TargetValidationIssue> 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<LauncherItem> items, List<string> parentNames, List<TargetValidationIssue> issues)
|
||||||
|
{
|
||||||
|
foreach (LauncherItem item in items)
|
||||||
|
{
|
||||||
|
List<string> 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.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 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<string> 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<string> 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)
|
private void About_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
AboutWindow aboutWindow = new()
|
AboutWindow aboutWindow = new()
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ Download the compiled Windows release from:
|
|||||||
- Right-click launcher entries to edit them in Settings
|
- Right-click launcher entries to edit them in Settings
|
||||||
- Right-click launcher entries to pin or unpin them from the top
|
- Right-click launcher entries to pin or unpin them from the top
|
||||||
- Right-click launcher entries or Settings items to temporarily disable them
|
- Right-click launcher entries or Settings items to temporarily disable them
|
||||||
|
- Right-click app/file entries to open their containing folder
|
||||||
|
- Validate missing app, file, and folder targets from Settings
|
||||||
- Right-click item duplication in Settings
|
- Right-click item duplication in Settings
|
||||||
- Right-click items in Settings to pin them to the top of their menu
|
- Right-click items in Settings to pin them to the top of their menu
|
||||||
- Per-item icon and display mode
|
- Per-item icon and display mode
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ public sealed class LauncherController : IDisposable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
popup = new LauncherPopup(Config, LaunchItem, OpenAllItems, ShowSettings, ShowSettings);
|
popup = new LauncherPopup(Config, LaunchItem, OpenAllItems, ShowSettings, ShowSettings, OpenContainingFolderFromPopup);
|
||||||
popup.Closed += (_, _) => popup = null;
|
popup.Closed += (_, _) => popup = null;
|
||||||
popup.ShowNearTaskbar();
|
popup.ShowNearTaskbar();
|
||||||
}
|
}
|
||||||
@@ -221,6 +221,50 @@ public sealed class LauncherController : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OpenContainingFolderFromPopup(LauncherItem item)
|
||||||
|
{
|
||||||
|
if (!OpenContainingFolder(item, out string? error) && !string.IsNullOrWhiteSpace(error))
|
||||||
|
{
|
||||||
|
System.Windows.MessageBox.Show(error, "Taskbar Launcher");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool OpenContainingFolder(LauncherItem item, out string? error)
|
||||||
|
{
|
||||||
|
error = null;
|
||||||
|
|
||||||
|
if (item.Type == LauncherItemType.Menu || string.IsNullOrWhiteSpace(item.Target))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string target = Environment.ExpandEnvironmentVariables(item.Target);
|
||||||
|
string? folderPath = Directory.Exists(target)
|
||||||
|
? target
|
||||||
|
: Path.GetDirectoryName(target);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(folderPath) || !Directory.Exists(folderPath))
|
||||||
|
{
|
||||||
|
error = $"{item.Title}: Containing folder was not found.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Process.Start(new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = folderPath,
|
||||||
|
UseShellExecute = true
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
error = $"{item.Title}: {ex.Message}";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static bool TryLaunchItem(LauncherItem item, out string? error)
|
private static bool TryLaunchItem(LauncherItem item, out string? error)
|
||||||
{
|
{
|
||||||
error = null;
|
error = null;
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
<Authors>Ray Berezowski</Authors>
|
<Authors>Ray Berezowski</Authors>
|
||||||
<ApplicationIcon>Assets\AppIcon.ico</ApplicationIcon>
|
<ApplicationIcon>Assets\AppIcon.ico</ApplicationIcon>
|
||||||
<Version>1.0.1</Version>
|
<Version>1.0.1</Version>
|
||||||
<AssemblyVersion>1.0.1.8</AssemblyVersion>
|
<AssemblyVersion>1.0.1.10</AssemblyVersion>
|
||||||
<FileVersion>1.0.1.8</FileVersion>
|
<FileVersion>1.0.1.10</FileVersion>
|
||||||
<InformationalVersion>1.0.1</InformationalVersion>
|
<InformationalVersion>1.0.1</InformationalVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,9 @@ public partial class LauncherPopup : Window
|
|||||||
private readonly Action<LauncherItem> openAllItems;
|
private readonly Action<LauncherItem> openAllItems;
|
||||||
private readonly Action showSettings;
|
private readonly Action showSettings;
|
||||||
private readonly Action<LauncherItem> editItem;
|
private readonly Action<LauncherItem> editItem;
|
||||||
|
private readonly Action<LauncherItem> openContainingFolder;
|
||||||
|
|
||||||
public LauncherPopup(LauncherConfig config, Action<LauncherItem> launchItem, Action<LauncherItem> openAllItems, Action showSettings, Action<LauncherItem> editItem)
|
public LauncherPopup(LauncherConfig config, Action<LauncherItem> launchItem, Action<LauncherItem> openAllItems, Action showSettings, Action<LauncherItem> editItem, Action<LauncherItem> openContainingFolder)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
this.config = config;
|
this.config = config;
|
||||||
@@ -29,6 +30,7 @@ public partial class LauncherPopup : Window
|
|||||||
this.openAllItems = openAllItems;
|
this.openAllItems = openAllItems;
|
||||||
this.showSettings = showSettings;
|
this.showSettings = showSettings;
|
||||||
this.editItem = editItem;
|
this.editItem = editItem;
|
||||||
|
this.openContainingFolder = openContainingFolder;
|
||||||
BuildItems();
|
BuildItems();
|
||||||
SizeChanged += (_, _) => QueueReposition();
|
SizeChanged += (_, _) => QueueReposition();
|
||||||
}
|
}
|
||||||
@@ -210,6 +212,7 @@ public partial class LauncherPopup : Window
|
|||||||
System.Windows.Controls.ContextMenu contextMenu = new();
|
System.Windows.Controls.ContextMenu contextMenu = new();
|
||||||
contextMenu.Items.Add(CreatePinMenuItem(item));
|
contextMenu.Items.Add(CreatePinMenuItem(item));
|
||||||
contextMenu.Items.Add(CreateDisableMenuItem(item));
|
contextMenu.Items.Add(CreateDisableMenuItem(item));
|
||||||
|
contextMenu.Items.Add(CreateOpenContainingFolderMenuItem(item));
|
||||||
contextMenu.Items.Add(CreateEditMenuItem(item));
|
contextMenu.Items.Add(CreateEditMenuItem(item));
|
||||||
return contextMenu;
|
return contextMenu;
|
||||||
}
|
}
|
||||||
@@ -250,6 +253,22 @@ public partial class LauncherPopup : Window
|
|||||||
return disableMenuItem;
|
return disableMenuItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private System.Windows.Controls.MenuItem CreateOpenContainingFolderMenuItem(LauncherItem item)
|
||||||
|
{
|
||||||
|
System.Windows.Controls.MenuItem openFolderMenuItem = new()
|
||||||
|
{
|
||||||
|
Header = "Open Containing Folder",
|
||||||
|
IsEnabled = CanOpenContainingFolder(item)
|
||||||
|
};
|
||||||
|
openFolderMenuItem.Click += (_, e) =>
|
||||||
|
{
|
||||||
|
openContainingFolder(item);
|
||||||
|
e.Handled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
return openFolderMenuItem;
|
||||||
|
}
|
||||||
|
|
||||||
private System.Windows.Controls.MenuItem CreateEditMenuItem(LauncherItem item)
|
private System.Windows.Controls.MenuItem CreateEditMenuItem(LauncherItem item)
|
||||||
{
|
{
|
||||||
System.Windows.Controls.MenuItem editMenuItem = new()
|
System.Windows.Controls.MenuItem editMenuItem = new()
|
||||||
@@ -271,6 +290,11 @@ public partial class LauncherPopup : Window
|
|||||||
return item.Type != LauncherItemType.Menu && !item.IsDisabled && !string.IsNullOrWhiteSpace(item.Target);
|
return item.Type != LauncherItemType.Menu && !item.IsDisabled && !string.IsNullOrWhiteSpace(item.Target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool CanOpenContainingFolder(LauncherItem item)
|
||||||
|
{
|
||||||
|
return item.Type is LauncherItemType.App or LauncherItemType.File && !string.IsNullOrWhiteSpace(item.Target);
|
||||||
|
}
|
||||||
|
|
||||||
private static IEnumerable<LauncherItem> GetDisplayItems(IEnumerable<LauncherItem> items)
|
private static IEnumerable<LauncherItem> GetDisplayItems(IEnumerable<LauncherItem> items)
|
||||||
{
|
{
|
||||||
return items
|
return items
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ Install the **.NET Desktop Runtime** for **Windows x64**.
|
|||||||
5. Right-click a launcher entry and choose **Edit in Settings** to jump to that item.
|
5. Right-click a launcher entry and choose **Edit in Settings** to jump to that item.
|
||||||
6. Right-click a launcher entry and choose **Pin to Top** or **Unpin from Top**.
|
6. Right-click a launcher entry and choose **Pin to Top** or **Unpin from Top**.
|
||||||
7. Right-click a launcher entry and choose **Disable Item** to hide it until it is enabled again from Settings.
|
7. Right-click a launcher entry and choose **Disable Item** to hide it until it is enabled again from Settings.
|
||||||
|
8. Right-click an app or file entry and choose **Open Containing Folder**.
|
||||||
|
|
||||||
Recently launched entries appear in the launcher popup under **Recent**.
|
Recently launched entries appear in the launcher popup under **Recent**.
|
||||||
|
|
||||||
@@ -39,6 +40,8 @@ From Settings you can:
|
|||||||
- Right-click an item and choose **Duplicate Item** to copy it
|
- Right-click an item and choose **Duplicate Item** to copy it
|
||||||
- Right-click an item and choose **Pin to Top** to show it first in its menu
|
- Right-click an item and choose **Pin to Top** to show it first in its menu
|
||||||
- Right-click an item and choose **Disable Item** or **Enable Item** to hide or restore it
|
- Right-click an item and choose **Disable Item** or **Enable Item** to hide or restore it
|
||||||
|
- Right-click an app or file item and choose **Open Containing Folder**
|
||||||
|
- Use **Config... > Validate Targets** to check for missing app, file, and folder targets
|
||||||
- Set per-item icon sizes
|
- Set per-item icon sizes
|
||||||
- Back up and restore `config.json`
|
- Back up and restore `config.json`
|
||||||
- Enable Start with Windows
|
- Enable Start with Windows
|
||||||
|
|||||||
Reference in New Issue
Block a user