diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index cd1cacf..c00a6f2 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -70,9 +70,24 @@ public partial class MainWindow : Window } } - private static TreeViewItem CreateTreeItem(LauncherItem item) + private TreeViewItem CreateTreeItem(LauncherItem item) { - var treeItem = new TreeViewItem { Header = $"{item.Title} ({item.Type})", Tag = item }; + var duplicateMenuItem = new System.Windows.Controls.MenuItem { Header = "Duplicate Item" }; + duplicateMenuItem.Click += DuplicateItem_Click; + + var treeItem = new TreeViewItem + { + Header = $"{item.Title} ({item.Type})", + Tag = item, + ContextMenu = new System.Windows.Controls.ContextMenu() + }; + treeItem.ContextMenu.Items.Add(duplicateMenuItem); + treeItem.PreviewMouseRightButtonDown += (_, e) => + { + treeItem.IsSelected = true; + e.Handled = false; + }; + foreach (LauncherItem child in item.Children) { treeItem.Items.Add(CreateTreeItem(child)); @@ -776,6 +791,32 @@ public partial class MainWindow : Window StatusText.Text = "Added submenu. Rename it, then Save."; } + 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 @@ -798,6 +839,32 @@ public partial class MainWindow : Window }; } + 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, + IconPath = item.IconPath, + 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) diff --git a/README.md b/README.md index bf166b6..1f4a256 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Download the compiled Windows release from: - Global hotkey, default `Ctrl+Alt+Space` - Search/filter in the launcher popup - Nested menus, drag/drop ordering, and drag/drop item creation in Settings +- Right-click item duplication in Settings - Per-item icon and display mode - Automatic icon selection from app/folder targets - Portable `config.json` diff --git a/TaskbarLauncher.csproj b/TaskbarLauncher.csproj index d1e9bdb..7f3c2b1 100644 --- a/TaskbarLauncher.csproj +++ b/TaskbarLauncher.csproj @@ -12,8 +12,8 @@ Ray Berezowski Assets\AppIcon.ico 1.0.1 - 1.0.1.2 - 1.0.1.2 + 1.0.1.3 + 1.0.1.3 1.0.1 diff --git a/docs/INSTALL.md b/docs/INSTALL.md index fb5d7c9..47eba31 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -31,6 +31,7 @@ From Settings you can: - Add folders, apps, files, and websites - Drag/drop items to reorder - Drag/drop files, folders, shortcuts, or web links into the launcher item list +- Right-click an item and choose **Duplicate Item** to copy it - Set per-item icon sizes - Back up and restore `config.json` - Enable Start with Windows