From 7691a3c326613beeae3e14e21327a365d8dad074 Mon Sep 17 00:00:00 2001 From: Tiberiu Chibici Date: Tue, 16 Sep 2014 21:57:15 +0300 Subject: [PATCH] Added project item commands, work on project manager, implemented project manager tests --- RainmeterStudio.Core/Model/Project.cs | 2 +- RainmeterStudio.Core/Model/Reference.cs | 81 ++- RainmeterStudio.Core/Model/Tree.cs | 154 +++++ .../RainmeterStudio.Core.csproj | 1 - RainmeterStudio.Core/Utils/DirectoryHelper.cs | 32 +- RainmeterStudio.Core/Utils/TreeExtensions.cs | 162 ----- .../Business/DocumentManagerTest.cs | 22 + .../Business/ProjectManagerClipboardTest.cs | 553 ++++++++++++++++++ .../Business/ProjectManagerTest.cs | 79 +++ .../Business/ProjectManagerTestBase.cs | 84 +++ RainmeterStudio.Tests/Model/ReferenceTest.cs | 193 +++++- .../RainmeterStudio.Tests.csproj | 4 + .../Storage/ProjectStorageTest.cs | 3 +- RainmeterStudio/Business/ProjectManager.cs | 261 ++++++++- ...ctStorage.cs => ProjectDocumentStorage.cs} | 61 +- RainmeterStudio/MainClass.cs | 7 +- RainmeterStudio/RainmeterStudio.csproj | 17 +- RainmeterStudio/Resources/Strings.Designer.cs | 207 +++++++ RainmeterStudio/Resources/Strings.resx | 77 ++- RainmeterStudio/Storage/ProjectStorage.cs | 69 +++ RainmeterStudio/UI/App.xaml | 8 +- .../UI/Controller/DocumentController.cs | 23 +- RainmeterStudio/UI/Controller/IconProvider.cs | 4 +- .../UI/Controller/ProjectController.cs | 186 ++++++ RainmeterStudio/UI/Dialogs/InputDialog.xaml | 29 + .../UI/Dialogs/InputDialog.xaml.cs | 380 ++++++++++++ RainmeterStudio/UI/MainWindow.xaml | 12 +- RainmeterStudio/UI/MainWindow.xaml.cs | 3 +- RainmeterStudio/UI/Panels/ProjectPanel.xaml | 3 + .../UI/Panels/ProjectPanel.xaml.cs | 130 +++- 30 files changed, 2526 insertions(+), 321 deletions(-) delete mode 100644 RainmeterStudio.Core/Utils/TreeExtensions.cs create mode 100644 RainmeterStudio.Tests/Business/DocumentManagerTest.cs create mode 100644 RainmeterStudio.Tests/Business/ProjectManagerClipboardTest.cs create mode 100644 RainmeterStudio.Tests/Business/ProjectManagerTest.cs create mode 100644 RainmeterStudio.Tests/Business/ProjectManagerTestBase.cs rename RainmeterStudio/Editor/ProjectEditor/{ProjectStorage.cs => ProjectDocumentStorage.cs} (51%) create mode 100644 RainmeterStudio/Storage/ProjectStorage.cs create mode 100644 RainmeterStudio/UI/Dialogs/InputDialog.xaml create mode 100644 RainmeterStudio/UI/Dialogs/InputDialog.xaml.cs diff --git a/RainmeterStudio.Core/Model/Project.cs b/RainmeterStudio.Core/Model/Project.cs index 9636c4d9..6283e04a 100644 --- a/RainmeterStudio.Core/Model/Project.cs +++ b/RainmeterStudio.Core/Model/Project.cs @@ -223,7 +223,7 @@ namespace RainmeterStudio.Core.Model /// public Project() { - Root = new Reference(String.Empty, Reference.ReferenceTargetKind.Project); + Root = new Reference(String.Empty, ReferenceTargetKind.Project); VariableFiles = new ObservableCollection(); Version = new Version(); MinimumRainmeter = new Version("3.1"); diff --git a/RainmeterStudio.Core/Model/Reference.cs b/RainmeterStudio.Core/Model/Reference.cs index 5d54d53a..2af87c7e 100644 --- a/RainmeterStudio.Core/Model/Reference.cs +++ b/RainmeterStudio.Core/Model/Reference.cs @@ -12,38 +12,38 @@ using RainmeterStudio.Core.Utils; namespace RainmeterStudio.Core.Model { + /// + /// The kind of item the reference points to + /// + public enum ReferenceTargetKind + { + /// + /// Invalid state + /// + None, + + /// + /// Reference points to a file + /// + File, + + /// + /// Reference points to a directory + /// + Directory, + + /// + /// Reference points to a project + /// + Project + } + /// /// Reference to a file or folder /// [DebuggerDisplay("QualifiedName = {QualifiedName}, StoragePath = {StoragePath}")] - public class Reference : INotifyCollectionChanged, INotifyPropertyChanged + public class Reference : INotifyCollectionChanged, INotifyPropertyChanged, ICloneable { - /// - /// The kind of item the reference points to - /// - public enum ReferenceTargetKind - { - /// - /// Invalid state - /// - None, - - /// - /// Reference points to a file - /// - File, - - /// - /// Reference points to a directory - /// - Directory, - - /// - /// Reference points to a project - /// - Project - } - private Dictionary _children; private Reference _parent; private string _name, _storagePath; @@ -90,7 +90,10 @@ namespace RainmeterStudio.Core.Model // Notify if (PropertyChanged != null) + { PropertyChanged(this, new PropertyChangedEventArgs("Parent")); + PropertyChanged(this, new PropertyChangedEventArgs("QualifiedName")); + } } } @@ -138,7 +141,10 @@ namespace RainmeterStudio.Core.Model _name = value; if (PropertyChanged != null) + { PropertyChanged(this, new PropertyChangedEventArgs("Name")); + PropertyChanged(this, new PropertyChangedEventArgs("QualifiedName")); + } } } @@ -216,7 +222,7 @@ namespace RainmeterStudio.Core.Model _kind = value; if (PropertyChanged != null) - PropertyChanged(this, new PropertyChangedEventArgs("Kind")); + PropertyChanged(this, new PropertyChangedEventArgs("TargetKind")); } } @@ -374,7 +380,7 @@ namespace RainmeterStudio.Core.Model private void Parent_PropertyChanged(object sender, PropertyChangedEventArgs e) { - if (PropertyChanged != null && (e.PropertyName == "Parent" || e.PropertyName == "Name" || e.PropertyName == "QualifiedName")) + if (PropertyChanged != null && e.PropertyName == "QualifiedName") PropertyChanged(this, new PropertyChangedEventArgs("QualifiedName")); } @@ -437,6 +443,23 @@ namespace RainmeterStudio.Core.Model } #endregion + + /// + /// Creates a clone of this reference + /// + /// The clone + /// The clone doesn't keep the parent. + public object Clone() + { + var cloneReference = new Reference(Name, StoragePath, TargetKind); + + foreach (var r in Children) + { + cloneReference.Add((Reference)r.Clone()); + } + + return cloneReference; + } } /// diff --git a/RainmeterStudio.Core/Model/Tree.cs b/RainmeterStudio.Core/Model/Tree.cs index 9cb56007..ed51546c 100644 --- a/RainmeterStudio.Core/Model/Tree.cs +++ b/RainmeterStudio.Core/Model/Tree.cs @@ -159,4 +159,158 @@ namespace RainmeterStudio.Core.Model throw new NotImplementedException(); } } + + /// + /// Extension methods for trees + /// + public static class TreeExtensions + { + /// + /// Tree traversal orders + /// + public enum TreeTraversalOrder + { + BreadthFirst, + DepthFirst, + DepthFirstPreOrder = DepthFirst, + DepthFirstPostOrder + } + + /// + /// Traverses a tree + /// + /// Tree data type + /// Root node of tree + /// Traversal order + /// An enumeration of the nodes in the specified traverse order + public static IEnumerable> Traverse(this Tree root, TreeTraversalOrder order = TreeTraversalOrder.BreadthFirst) + { + if (order == TreeTraversalOrder.BreadthFirst) + return TraverseBF(root); + + else return TraverseDF(root, order); + } + + private static IEnumerable> TraverseDF(this Tree root, TreeTraversalOrder order) + { + // Preorder - return root first + if (order == TreeTraversalOrder.DepthFirstPreOrder) + yield return root; + + // Return children + foreach (var child in root.Children) + foreach (var node in TraverseDF(child, order)) + yield return node; + + // Postorder - return root last + if (order == TreeTraversalOrder.DepthFirstPostOrder) + yield return root; + } + + private static IEnumerable> TraverseBF(this Tree root) + { + // Create a queue containing the root + Queue> queue = new Queue>(); + queue.Enqueue(root); + + // While there are elements in the queue + while (queue.Count > 0) + { + // Return next node in tree + var node = queue.Dequeue(); + yield return node; + + // Enqueue node's children + foreach (var child in node.Children) + queue.Enqueue(child); + } + } + + /// + /// Applies an action to every node of the tree + /// + /// Tree data type + /// Root node of tree + /// Action to apply + /// Traversal order + public static void Apply(this Tree root, Action> action, TreeTraversalOrder order = TreeTraversalOrder.BreadthFirst) + { + // Safety check + if (action == null) + return; + + // Apply action + foreach (var node in Traverse(root, order)) + action(node); + } + + /// + /// Applies an action to every node of the tree + /// + /// Tree data type + /// Root node of tree + /// Action to apply + /// Traversal order + public static void ApplyToData(this Tree root, Action action, TreeTraversalOrder order = TreeTraversalOrder.BreadthFirst) + where T : class + { + // Safety check + if (action == null) + return; + + // Apply action + foreach (var node in Traverse(root, order)) + action(node.Data); + } + + /// + /// Rebuilds the tree by applying the specified transform function + /// + /// Data type of tree + /// Data type of rebuilt tree + /// The root node + /// The transform function + /// The transformed tree + public static Tree Transform(this Tree root, Func, Tree> transformFunction) + { + // Safety check + if (transformFunction == null) + throw new ArgumentNullException("Transform function cannot be null."); + + // Build root + Tree resRoot = transformFunction(root); + + // Add children + foreach (var node in root.Children) + resRoot.Children.Add(Transform(node, transformFunction)); + + // Return + return resRoot; + } + + /// + /// Rebuilds the tree by applying the specified transform function + /// + /// Data type of tree + /// Data type of rebuilt tree + /// The root node + /// The transform function + /// The transformed tree + public static Tree TransformData(this Tree root, Func transformFunction) + { + // Safety check + if (transformFunction == null) + throw new ArgumentNullException("Transform function cannot be null."); + + // Build root + Tree resRoot = new Tree(transformFunction(root.Data)); + + // Add children + foreach (var node in root.Children) + resRoot.Children.Add(TransformData(node, transformFunction)); + + // Return + return resRoot; + } + } } diff --git a/RainmeterStudio.Core/RainmeterStudio.Core.csproj b/RainmeterStudio.Core/RainmeterStudio.Core.csproj index a6b531e0..4adacb8d 100644 --- a/RainmeterStudio.Core/RainmeterStudio.Core.csproj +++ b/RainmeterStudio.Core/RainmeterStudio.Core.csproj @@ -71,7 +71,6 @@ - diff --git a/RainmeterStudio.Core/Utils/DirectoryHelper.cs b/RainmeterStudio.Core/Utils/DirectoryHelper.cs index 68813782..80df9426 100644 --- a/RainmeterStudio.Core/Utils/DirectoryHelper.cs +++ b/RainmeterStudio.Core/Utils/DirectoryHelper.cs @@ -17,11 +17,13 @@ namespace RainmeterStudio.Core.Utils public static Reference GetFolderTree(string folder) { // Build tree object - Reference refTree = new Reference(Path.GetFileName(folder), folder); + Reference refTree = new Reference(Path.GetFileName(folder), folder, ReferenceTargetKind.File); // Navigate folder structure if (Directory.Exists(folder)) { + refTree.TargetKind = ReferenceTargetKind.Directory; + foreach (var item in Directory.EnumerateDirectories(folder) .Concat(Directory.EnumerateFiles(folder))) { @@ -46,5 +48,33 @@ namespace RainmeterStudio.Core.Utils return String.Equals(path1, path2, StringComparison.InvariantCultureIgnoreCase); } + + /// + /// Copies a directory from source to destination + /// + /// Directory to copy + /// Destination directory + /// + /// If destination exists, the contents of 'source' will be copied to destination. + /// Else, destination will be created, and the contents of source will be copied to destination. + public static void CopyDirectory(string source, string destination, bool merge = false) + { + if (source == destination) + throw new IOException("You cannot copy a folder in the same folder."); + + if (Directory.Exists(destination) && !merge) + throw new IOException("Destination folder already exists."); + + foreach (var file in Directory.EnumerateFiles(source, "*", SearchOption.AllDirectories)) + { + string newFile = file.StartsWith(source) ? Path.Combine(destination, file.Substring(source.Length).Trim('\\')) : file; + string newDirectory = Path.GetDirectoryName(newFile); + + if (!String.IsNullOrEmpty(newDirectory) && !Directory.Exists(newDirectory)) + Directory.CreateDirectory(newDirectory); + + File.Copy(file, newFile); + } + } } } diff --git a/RainmeterStudio.Core/Utils/TreeExtensions.cs b/RainmeterStudio.Core/Utils/TreeExtensions.cs deleted file mode 100644 index 29ccd246..00000000 --- a/RainmeterStudio.Core/Utils/TreeExtensions.cs +++ /dev/null @@ -1,162 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using RainmeterStudio.Core.Model; - -namespace RainmeterStudio.Core.Utils -{ - /// - /// Extension methods for trees - /// - public static class TreeExtensions - { - /// - /// Tree traversal orders - /// - public enum TreeTraversalOrder - { - BreadthFirst, - DepthFirst, - DepthFirstPreOrder = DepthFirst, - DepthFirstPostOrder - } - - /// - /// Traverses a tree - /// - /// Tree data type - /// Root node of tree - /// Traversal order - /// An enumeration of the nodes in the specified traverse order - public static IEnumerable> Traverse(this Tree root, TreeTraversalOrder order = TreeTraversalOrder.BreadthFirst) - { - if (order == TreeTraversalOrder.BreadthFirst) - return TraverseBF(root); - - else return TraverseDF(root, order); - } - - private static IEnumerable> TraverseDF(this Tree root, TreeTraversalOrder order) - { - // Preorder - return root first - if (order == TreeTraversalOrder.DepthFirstPreOrder) - yield return root; - - // Return children - foreach (var child in root.Children) - foreach (var node in TraverseDF(child, order)) - yield return node; - - // Postorder - return root last - if (order == TreeTraversalOrder.DepthFirstPostOrder) - yield return root; - } - - private static IEnumerable> TraverseBF(this Tree root) - { - // Create a queue containing the root - Queue> queue = new Queue>(); - queue.Enqueue(root); - - // While there are elements in the queue - while (queue.Count > 0) - { - // Return next node in tree - var node = queue.Dequeue(); - yield return node; - - // Enqueue node's children - foreach (var child in node.Children) - queue.Enqueue(child); - } - } - - /// - /// Applies an action to every node of the tree - /// - /// Tree data type - /// Root node of tree - /// Action to apply - /// Traversal order - public static void Apply(this Tree root, Action> action, TreeTraversalOrder order = TreeTraversalOrder.BreadthFirst) - { - // Safety check - if (action == null) - return; - - // Apply action - foreach (var node in Traverse(root, order)) - action(node); - } - - /// - /// Applies an action to every node of the tree - /// - /// Tree data type - /// Root node of tree - /// Action to apply - /// Traversal order - public static void ApplyToData(this Tree root, Action action, TreeTraversalOrder order = TreeTraversalOrder.BreadthFirst) - where T : class - { - // Safety check - if (action == null) - return; - - // Apply action - foreach (var node in Traverse(root, order)) - action(node.Data); - } - - /// - /// Rebuilds the tree by applying the specified transform function - /// - /// Data type of tree - /// Data type of rebuilt tree - /// The root node - /// The transform function - /// The transformed tree - public static Tree Transform(this Tree root, Func, Tree> transformFunction) - { - // Safety check - if (transformFunction == null) - throw new ArgumentNullException("Transform function cannot be null."); - - // Build root - Tree resRoot = transformFunction(root); - - // Add children - foreach (var node in root.Children) - resRoot.Children.Add(Transform(node, transformFunction)); - - // Return - return resRoot; - } - - /// - /// Rebuilds the tree by applying the specified transform function - /// - /// Data type of tree - /// Data type of rebuilt tree - /// The root node - /// The transform function - /// The transformed tree - public static Tree TransformData(this Tree root, Func transformFunction) - { - // Safety check - if (transformFunction == null) - throw new ArgumentNullException("Transform function cannot be null."); - - // Build root - Tree resRoot = new Tree(transformFunction(root.Data)); - - // Add children - foreach (var node in root.Children) - resRoot.Children.Add(TransformData(node, transformFunction)); - - // Return - return resRoot; - } - } -} diff --git a/RainmeterStudio.Tests/Business/DocumentManagerTest.cs b/RainmeterStudio.Tests/Business/DocumentManagerTest.cs new file mode 100644 index 00000000..9e673a64 --- /dev/null +++ b/RainmeterStudio.Tests/Business/DocumentManagerTest.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RainmeterStudio.Business; + +namespace RainmeterStudio.Tests.Business +{ + [TestClass] + public class DocumentManagerTest + { + DocumentManager documentManager; + + [TestInitialize] + public void Initialize() + { + documentManager = new DocumentManager(); + } + } +} diff --git a/RainmeterStudio.Tests/Business/ProjectManagerClipboardTest.cs b/RainmeterStudio.Tests/Business/ProjectManagerClipboardTest.cs new file mode 100644 index 00000000..2cabd229 --- /dev/null +++ b/RainmeterStudio.Tests/Business/ProjectManagerClipboardTest.cs @@ -0,0 +1,553 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RainmeterStudio.Core.Model; + +namespace RainmeterStudio.Tests.Business +{ + /// + /// Tests the project item operations for the project manager + /// + /// + /// Initial project structure: + /// root + /// + folder1 + /// | + sub1 + /// | | + file2.png + /// | | + file3.txt + /// | + file1.txt + /// + folder2 + /// + file1.txt + /// + sub1 + /// + [TestClass] + public class ProjectManagerProjectItemOperationsTest : ProjectManagerTestBase + { + private Reference Root + { + get + { + return ProjectManager.ActiveProject.Root; + } + } + + /// + /// Sets up test + /// + public override void OnInitialize() + { + // Create a new project + string projectName = "test"; + string projectPath = projectName + ".rsproj"; + + ProjectManager.CreateProject(projectName, projectPath, ProjectTemplate); + + // Create a project structure + var root = ProjectManager.ActiveProject.Root; + ProjectManager.CreateFolder("folder1", root); + ProjectManager.CreateFolder("folder2", root); + ProjectManager.CreateFolder("sub1", root.Children[0]); + ProjectManager.CreateFolder("sub1", root); + + File.Create("file1.txt").Close(); + File.Create("folder1\\file1.txt").Close(); + File.Create("folder1\\sub1\\file2.png").Close(); + File.Create("folder1\\sub1\\file3.txt").Close(); + + root.Add(new Reference("file1.txt", "file1.txt")); + root.Children[0].Add(new Reference("file1.txt", "folder1\\file1.txt")); + root.Children[0].Children[0].Add(new Reference("file2.png", "folder1\\sub1\\file2.png")); + root.Children[0].Children[0].Add(new Reference("file3.txt", "folder1\\sub1\\file3.txt")); + + } + + #region Cut & paste for files + + [TestMethod] + public void ProjectManagerCutPasteFileTest() + { + var folder1 = Root.GetReference("test/folder1"); + var folder2 = Root.GetReference("test/folder2"); + var file1 = Root.GetReference("test/folder1/file1.txt"); + var file2 = Root.GetReference("test/folder1/sub1/file2.png"); + + // Initial state + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(0, folder2.Count); + Assert.AreEqual(folder1, file1.Parent); + + // Cut + ProjectManager.ProjectItemCutClipboard(file1); + + // The item should be in the clipboard, but state unchanged + Assert.IsTrue(ProjectManager.HaveProjectItemInClipboard()); + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(0, folder2.Count); + Assert.AreEqual(folder1, file1.Parent); + + // Paste in folder 2 + ProjectManager.ProjectItemPasteClipboard(folder2); + Assert.IsFalse(ProjectManager.HaveProjectItemInClipboard()); + Assert.AreEqual(1, folder1.Count); + Assert.AreEqual(1, folder2.Count); + Assert.AreEqual(folder2, file1.Parent); + + // Cut and paste in root + ProjectManager.ProjectItemCutClipboard(file2); + ProjectManager.ProjectItemPasteClipboard(Root); + + Assert.AreEqual(Root, file2.Parent); + } + + [TestMethod] + public void ProjectManagerCutPasteFileNameConflictTest() + { + var folder1 = Root.GetReference("test/folder1"); + var file1 = Root.GetReference("test/folder1/file1.txt"); + + // Initial state + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(4, Root.Count); + Assert.AreEqual(folder1, file1.Parent); + + // Cut + ProjectManager.ProjectItemCutClipboard(file1); + + // The item should be in the clipboard, but state unchanged + Assert.IsTrue(ProjectManager.HaveProjectItemInClipboard()); + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(4, Root.Count); + Assert.AreEqual(folder1, file1.Parent); + + // Paste in root + try + { + ProjectManager.ProjectItemPasteClipboard(Root); + Assert.Fail("File should already exist, should not overwrite."); + } + catch (IOException) + { + } + + // Item shouldn't be in clipboard any more, but state should remain the same + Assert.IsTrue(ProjectManager.HaveProjectItemInClipboard()); + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(4, Root.Count); + Assert.AreEqual(folder1, file1.Parent); + } + + [TestMethod] + public void ProjectManagerCutPasteFileSameDirectoryTest() + { + var folder1 = Root.GetReference("test/folder1"); + var file1 = Root.GetReference("test/folder1/file1.txt"); + + // Initial state + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(folder1, file1.Parent); + + // Cut + ProjectManager.ProjectItemCutClipboard(file1); + + // The item should be in the clipboard, but state unchanged + Assert.IsTrue(ProjectManager.HaveProjectItemInClipboard()); + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(folder1, file1.Parent); + + // Paste in root + ProjectManager.ProjectItemPasteClipboard(folder1); + + // Item shouldn't be in clipboard any more, but state should remain the same + Assert.IsFalse(ProjectManager.HaveProjectItemInClipboard()); + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(folder1, file1.Parent); + } + + #endregion + + #region Cut & paste for directories + + [TestMethod] + public void ProjectManagerCutPasteDirectoryTest() + { + var folder1 = Root.GetReference("test/folder1"); + var folder2 = Root.GetReference("test/folder2"); + var sub1 = Root.GetReference("test/folder1/sub1"); + + // Initial state + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(0, folder2.Count); + Assert.AreEqual(2, sub1.Count); + Assert.IsFalse(sub1.Children.Any(x => !File.Exists(x.StoragePath))); + Assert.AreEqual(folder1, sub1.Parent); + Assert.AreEqual("test/folder1/sub1/file2.png", sub1.Children[0].QualifiedName); + Assert.AreEqual("folder1\\sub1\\file2.png", sub1.Children[0].StoragePath); + + // Cut + ProjectManager.ProjectItemCutClipboard(sub1); + + // The item should be in the clipboard, but state unchanged + Assert.IsTrue(ProjectManager.HaveProjectItemInClipboard()); + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(0, folder2.Count); + Assert.AreEqual(2, sub1.Count); + Assert.IsFalse(sub1.Children.Any(x => !File.Exists(x.StoragePath))); + Assert.AreEqual(folder1, sub1.Parent); + + // Paste in folder 2 + ProjectManager.ProjectItemPasteClipboard(folder2); + Assert.IsFalse(ProjectManager.HaveProjectItemInClipboard()); + Assert.AreEqual(1, folder1.Count); + Assert.AreEqual(1, folder2.Count); + Assert.AreEqual(2, sub1.Count); + Assert.IsFalse(sub1.Children.Any(x => !File.Exists(x.StoragePath))); + Assert.AreEqual(folder2, sub1.Parent); + Assert.AreEqual("test/folder2/sub1/file2.png", sub1.Children[0].QualifiedName); + Assert.AreEqual("folder2\\sub1\\file2.png", sub1.Children[0].StoragePath); + } + + [TestMethod] + public void ProjectManagerCutPasteDirectoryNameConflictTest() + { + var folder1 = Root.GetReference("test/folder1"); + var sub1 = Root.GetReference("test/folder1/sub1"); + + // Initial state + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(4, Root.Count); + Assert.AreEqual(2, sub1.Count); + Assert.IsFalse(sub1.Children.Any(x => !File.Exists(x.StoragePath))); + Assert.AreEqual(folder1, sub1.Parent); + Assert.AreEqual("test/folder1/sub1/file2.png", sub1.Children[0].QualifiedName); + Assert.AreEqual("folder1\\sub1\\file2.png", sub1.Children[0].StoragePath); + + // Cut + ProjectManager.ProjectItemCutClipboard(sub1); + + // The item should be in the clipboard, but state unchanged + Assert.IsTrue(ProjectManager.HaveProjectItemInClipboard()); + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(4, Root.Count); + Assert.AreEqual(2, sub1.Count); + Assert.IsFalse(sub1.Children.Any(x => !File.Exists(x.StoragePath))); + Assert.AreEqual(folder1, sub1.Parent); + + // Paste in root + try + { + ProjectManager.ProjectItemPasteClipboard(Root); + Assert.Fail("Directory already exists, should not overwrite."); + } + catch (IOException) + { + } + + // Object remains in clipboard, state unchanged + Assert.IsTrue(ProjectManager.HaveProjectItemInClipboard()); + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(4, Root.Count); + Assert.AreEqual(2, sub1.Count); + Assert.IsFalse(sub1.Children.Any(x => !File.Exists(x.StoragePath))); + Assert.AreEqual(folder1, sub1.Parent); + Assert.AreEqual("test/folder1/sub1/file2.png", sub1.Children[0].QualifiedName); + Assert.AreEqual("folder1\\sub1\\file2.png", sub1.Children[0].StoragePath); + } + + [TestMethod] + public void ProjectManagerCutPasteDirectorySameDirectoryTest() + { + var folder1 = Root.GetReference("test/folder1"); + var sub1 = Root.GetReference("test/folder1/sub1"); + + // Initial state + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(2, sub1.Count); + Assert.IsFalse(sub1.Children.Any(x => !File.Exists(x.StoragePath))); + Assert.AreEqual(folder1, sub1.Parent); + Assert.AreEqual("test/folder1/sub1/file2.png", sub1.Children[0].QualifiedName); + Assert.AreEqual("folder1\\sub1\\file2.png", sub1.Children[0].StoragePath); + + // Cut + ProjectManager.ProjectItemCutClipboard(sub1); + + // The item should be in the clipboard, but state unchanged + Assert.IsTrue(ProjectManager.HaveProjectItemInClipboard()); + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(2, sub1.Count); + Assert.IsFalse(sub1.Children.Any(x => !File.Exists(x.StoragePath))); + Assert.AreEqual(folder1, sub1.Parent); + + // Paste in folder 2 + try + { + ProjectManager.ProjectItemPasteClipboard(folder1); + Assert.Fail("Directory is same."); + } + catch (IOException) + { + } + + Assert.IsTrue(ProjectManager.HaveProjectItemInClipboard()); + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(2, sub1.Count); + Assert.IsFalse(sub1.Children.Any(x => !File.Exists(x.StoragePath))); + Assert.AreEqual(folder1, sub1.Parent); + Assert.AreEqual("test/folder1/sub1/file2.png", sub1.Children[0].QualifiedName); + Assert.AreEqual("folder1\\sub1\\file2.png", sub1.Children[0].StoragePath); + } + + #endregion + + #region Copy & paste for files + + [TestMethod] + public void ProjectManagerCopyPasteFileTest() + { + var folder1 = Root.GetReference("test/folder1"); + var folder2 = Root.GetReference("test/folder2"); + var file1 = Root.GetReference("test/folder1/file1.txt"); + + // Initial state + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(0, folder2.Count); + Assert.AreEqual(folder1, file1.Parent); + + // Copy + ProjectManager.ProjectItemCopyClipboard(file1); + + // The item should be in the clipboard, but state unchanged + Assert.IsTrue(ProjectManager.HaveProjectItemInClipboard()); + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(0, folder2.Count); + Assert.AreEqual(folder1, file1.Parent); + + // Paste in folder 2 + ProjectManager.ProjectItemPasteClipboard(folder2); + Assert.IsTrue(ProjectManager.HaveProjectItemInClipboard()); + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(1, folder2.Count); + Assert.AreEqual(folder1, file1.Parent); + } + + [TestMethod] + public void ProjectManagerCopyPasteFileNameConflictTest() + { + Reference folder1 = Root.GetReference("test/folder1"); + Reference file1 = Root.GetReference("test/folder1/file1.txt"); + Reference copy; + + // Initial state + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(4, Root.Count); + Assert.AreEqual(folder1, file1.Parent); + + // Copy + ProjectManager.ProjectItemCopyClipboard(file1); + + // The item should be in the clipboard, but state unchanged + Assert.IsTrue(ProjectManager.HaveProjectItemInClipboard()); + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(4, Root.Count); + Assert.AreEqual(folder1, file1.Parent); + + // Paste in root + ProjectManager.ProjectItemPasteClipboard(Root); + + Assert.IsTrue(ProjectManager.HaveProjectItemInClipboard()); + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(5, Root.Count); + Assert.AreEqual(folder1, file1.Parent); + Assert.IsTrue(Root.TryGetReference("test/file1_2.txt", out copy)); + + // Paste again in root + ProjectManager.ProjectItemPasteClipboard(Root); + + Assert.IsTrue(ProjectManager.HaveProjectItemInClipboard()); + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(6, Root.Count); + Assert.AreEqual(folder1, file1.Parent); + Assert.IsTrue(Root.TryGetReference("test/file1_3.txt", out copy)); + } + + [TestMethod] + public void ProjectManagerCopyPasteFileSameDirectoryTest() + { + var folder1 = Root.GetReference("test/folder1"); + var file1 = Root.GetReference("test/folder1/file1.txt"); + + // Initial state + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(folder1, file1.Parent); + + // Copy + ProjectManager.ProjectItemCopyClipboard(file1); + + // The item should be in the clipboard, but state unchanged + Assert.IsTrue(ProjectManager.HaveProjectItemInClipboard()); + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(folder1, file1.Parent); + + // Paste in root + ProjectManager.ProjectItemPasteClipboard(folder1); + + Assert.IsTrue(ProjectManager.HaveProjectItemInClipboard()); + Assert.AreEqual(3, folder1.Count); + Assert.AreEqual(folder1, file1.Parent); + Assert.IsTrue(Root.TryGetReference("test/folder1/file1_2.txt", out file1)); + + // Past again in root + ProjectManager.ProjectItemPasteClipboard(folder1); + + Assert.IsTrue(ProjectManager.HaveProjectItemInClipboard()); + Assert.AreEqual(4, folder1.Count); + Assert.AreEqual(folder1, file1.Parent); + Assert.IsTrue(Root.TryGetReference("test/folder1/file1_3.txt", out file1)); + } + + #endregion + + #region Copy & paste for directories + + [TestMethod] + public void ProjectManagerCopyPasteDirectoryTest() + { + var folder1 = Root.GetReference("test/folder1"); + var folder2 = Root.GetReference("test/folder2"); + var sub1 = Root.GetReference("test/folder1/sub1"); + + // Initial state + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(0, folder2.Count); + Assert.AreEqual(2, sub1.Count); + Assert.IsFalse(sub1.Children.Any(x => !File.Exists(x.StoragePath))); + Assert.AreEqual(folder1, sub1.Parent); + Assert.AreEqual("test/folder1/sub1/file2.png", sub1.Children[0].QualifiedName); + Assert.AreEqual("folder1\\sub1\\file2.png", sub1.Children[0].StoragePath); + + // Copy + ProjectManager.ProjectItemCopyClipboard(sub1); + + // The item should be in the clipboard, but state unchanged + Assert.IsTrue(ProjectManager.HaveProjectItemInClipboard()); + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(0, folder2.Count); + Assert.AreEqual(2, sub1.Count); + Assert.IsFalse(sub1.Children.Any(x => !File.Exists(x.StoragePath))); + Assert.AreEqual(folder1, sub1.Parent); + + // Paste in folder 2 + ProjectManager.ProjectItemPasteClipboard(folder2); + Reference sub2; + Assert.IsTrue(Root.TryGetReference("test/folder2/sub1", out sub2)); + Assert.IsTrue(ProjectManager.HaveProjectItemInClipboard()); + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(1, folder2.Count); + Assert.AreEqual(2, sub1.Count); + Assert.AreEqual(2, sub2.Count); + Assert.IsFalse(sub1.Children.Any(x => !File.Exists(x.StoragePath))); + Assert.IsFalse(sub2.Children.Any(x => !File.Exists(x.StoragePath))); + Assert.AreEqual(folder1, sub1.Parent); + Assert.AreEqual(folder2, sub2.Parent); + Assert.AreEqual("test/folder1/sub1/file2.png", sub1.Children[0].QualifiedName); + Assert.AreEqual("folder1\\sub1\\file2.png", sub1.Children[0].StoragePath); + Assert.AreEqual("test/folder2/sub1/file2.png", sub2.Children[0].QualifiedName); + Assert.AreEqual("folder2\\sub1\\file2.png", sub2.Children[0].StoragePath); + } + + [TestMethod] + public void ProjectManagerCopyPasteDirectoryNameConflictTest() + { + var folder1 = Root.GetReference("test/folder1"); + var sub1 = Root.GetReference("test/folder1/sub1"); + + // Initial state + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(4, Root.Count); + Assert.AreEqual(2, sub1.Count); + Assert.IsFalse(sub1.Children.Any(x => !File.Exists(x.StoragePath))); + Assert.AreEqual(folder1, sub1.Parent); + Assert.AreEqual("test/folder1/sub1/file2.png", sub1.Children[0].QualifiedName); + Assert.AreEqual("folder1\\sub1\\file2.png", sub1.Children[0].StoragePath); + + // Copy + ProjectManager.ProjectItemCopyClipboard(sub1); + + // The item should be in the clipboard, but state unchanged + Assert.IsTrue(ProjectManager.HaveProjectItemInClipboard()); + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(4, Root.Count); + Assert.AreEqual(2, sub1.Count); + Assert.IsFalse(sub1.Children.Any(x => !File.Exists(x.StoragePath))); + Assert.AreEqual(folder1, sub1.Parent); + + // Paste in root + try + { + ProjectManager.ProjectItemPasteClipboard(Root); + Assert.Fail("Directory already exists, should not overwrite."); + } + catch (IOException) + { + } + + // Object remains in clipboard, state unchanged + Assert.IsTrue(ProjectManager.HaveProjectItemInClipboard()); + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(4, Root.Count); + Assert.AreEqual(2, sub1.Count); + Assert.IsFalse(sub1.Children.Any(x => !File.Exists(x.StoragePath))); + Assert.AreEqual(folder1, sub1.Parent); + Assert.AreEqual("test/folder1/sub1/file2.png", sub1.Children[0].QualifiedName); + Assert.AreEqual("folder1\\sub1\\file2.png", sub1.Children[0].StoragePath); + } + + [TestMethod] + public void ProjectManagerCopyPasteDirectorySameDirectoryTest() + { + var folder1 = Root.GetReference("test/folder1"); + var sub1 = Root.GetReference("test/folder1/sub1"); + + // Initial state + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(2, sub1.Count); + Assert.IsFalse(sub1.Children.Any(x => !File.Exists(x.StoragePath))); + Assert.AreEqual(folder1, sub1.Parent); + Assert.AreEqual("test/folder1/sub1/file2.png", sub1.Children[0].QualifiedName); + Assert.AreEqual("folder1\\sub1\\file2.png", sub1.Children[0].StoragePath); + + // Copy + ProjectManager.ProjectItemCopyClipboard(sub1); + + // The item should be in the clipboard, but state unchanged + Assert.IsTrue(ProjectManager.HaveProjectItemInClipboard()); + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(2, sub1.Count); + Assert.IsFalse(sub1.Children.Any(x => !File.Exists(x.StoragePath))); + Assert.AreEqual(folder1, sub1.Parent); + + // Paste in folder 2 + try + { + ProjectManager.ProjectItemPasteClipboard(folder1); + Assert.Fail("Directory is same."); + } + catch (IOException) + { + } + + Assert.IsTrue(ProjectManager.HaveProjectItemInClipboard()); + Assert.AreEqual(2, folder1.Count); + Assert.AreEqual(2, sub1.Count); + Assert.IsFalse(sub1.Children.Any(x => !File.Exists(x.StoragePath))); + Assert.AreEqual(folder1, sub1.Parent); + Assert.AreEqual("test/folder1/sub1/file2.png", sub1.Children[0].QualifiedName); + Assert.AreEqual("folder1\\sub1\\file2.png", sub1.Children[0].StoragePath); + } + + #endregion + } +} diff --git a/RainmeterStudio.Tests/Business/ProjectManagerTest.cs b/RainmeterStudio.Tests/Business/ProjectManagerTest.cs new file mode 100644 index 00000000..3cfe99c9 --- /dev/null +++ b/RainmeterStudio.Tests/Business/ProjectManagerTest.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RainmeterStudio.Business; +using RainmeterStudio.Core.Model; + +namespace RainmeterStudio.Tests.Business +{ + [TestClass] + public class ProjectManagerTest : ProjectManagerTestBase + { + /// + /// Tests if the sample template is registered + /// + [TestMethod] + public void ProjectManagerTemplatesTest() + { + Assert.AreEqual(1, ProjectManager.ProjectTemplates.Count()); + Assert.AreEqual(ProjectTemplate, ProjectManager.ProjectTemplates.First()); + } + + /// + /// Tests the project create functionality + /// + [TestMethod] + public void ProjectManagerCreateProjectTest() + { + bool loaded = false; + string projName = "test"; + string projPath = TestContext.TestName + ".rsproj"; + + ProjectManager.ActiveProjectChanged += new EventHandler((sender, e) => loaded = true); + ProjectManager.CreateProject(projName, projPath, ProjectTemplate); + + Assert.IsTrue(loaded); + Assert.AreEqual(projName, ProjectManager.ActiveProject.Name); + Assert.AreEqual(projPath, ProjectManager.ActiveProject.Path); + Assert.IsTrue(File.Exists(projPath)); + } + + /// + /// Tests the open project functionality + /// + [TestMethod] + public void ProjectManagerOpenProjectTest() + { + // Create a new project + bool changed = false; + string projName = "test"; + string projPath = TestContext.TestName + ".rsproj"; + + ProjectManager.ActiveProjectChanged += new EventHandler((sender, e) => changed = true); + ProjectManager.CreateProject(projName, projPath, ProjectTemplate); + + // Reopen new project + changed = false; + ProjectManager.OpenProject(projPath); + Assert.IsTrue(changed); + + // Close project + changed = false; + ProjectManager.Close(); + Assert.IsTrue(changed); + + // Open a copy + changed = false; + Directory.CreateDirectory("projectDir"); + string proj2Path = Path.Combine("projectDir", TestContext.TestName + "2.rsproj"); + File.Copy(projPath, proj2Path); + ProjectManager.OpenProject(proj2Path); + Assert.AreEqual(proj2Path, ProjectManager.ActiveProject.Path); + Assert.AreEqual(projName, ProjectManager.ActiveProject.Name); + } + } +} diff --git a/RainmeterStudio.Tests/Business/ProjectManagerTestBase.cs b/RainmeterStudio.Tests/Business/ProjectManagerTestBase.cs new file mode 100644 index 00000000..b53ce075 --- /dev/null +++ b/RainmeterStudio.Tests/Business/ProjectManagerTestBase.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RainmeterStudio.Business; +using RainmeterStudio.Core.Model; + +namespace RainmeterStudio.Tests.Business +{ + /// + /// Common stuff for project manager tests + /// + public class ProjectManagerTestBase + { + #region Project template + + /// + /// A sample project template + /// + protected class TestTemplate : IProjectTemplate + { + public string Name + { + get { return "TestTemplate"; } + } + + public IEnumerable Properties + { + get { return Enumerable.Empty(); } + } + + public Project CreateProject() + { + return new Project(); + } + } + + #endregion + + /// + /// Gets or sets the text context + /// + public TestContext TestContext { get; set; } + + /// + /// Gets or sets the project manager + /// + protected virtual ProjectManager ProjectManager { get; set; } + + /// + /// Gets or sets the sample project template + /// + protected virtual IProjectTemplate ProjectTemplate { get; set; } + + /// + /// Sets up the test + /// + [TestInitialize] + public void Initialize() + { + string testDirectory = Path.Combine(TestContext.DeploymentDirectory, TestContext.TestName); + + Directory.CreateDirectory(testDirectory); + Directory.SetCurrentDirectory(testDirectory); + + // Set up project manager + ProjectManager = new ProjectManager(); + + // Set up project template + ProjectTemplate = new TestTemplate(); + ProjectManager.RegisterProjectTemplate(ProjectTemplate); + + OnInitialize(); + } + + public virtual void OnInitialize() + { + + } + } +} diff --git a/RainmeterStudio.Tests/Model/ReferenceTest.cs b/RainmeterStudio.Tests/Model/ReferenceTest.cs index f12d6e6e..5de98b46 100644 --- a/RainmeterStudio.Tests/Model/ReferenceTest.cs +++ b/RainmeterStudio.Tests/Model/ReferenceTest.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -14,14 +16,22 @@ namespace RainmeterStudio.Tests.Model [TestClass] public class ReferenceTest { + public TestContext TestContext { get; set; } + + [TestInitialize] + public void Initialize() + { + Directory.SetCurrentDirectory(TestContext.DeploymentDirectory); + } + /// /// Tests the constructors of the reference class /// [TestMethod] public void ReferenceConstructorTest() { - Reference root = new Reference("root", "D:\\Data\\Project", Reference.ReferenceTargetKind.Directory); - Reference file = new Reference("f ile__asdf.txt", Reference.ReferenceTargetKind.File); + Reference root = new Reference("root", "D:\\Data\\Project", ReferenceTargetKind.Directory); + Reference file = new Reference("f ile__asdf.txt", ReferenceTargetKind.File); // Test root Assert.AreEqual("root", root.Name); @@ -29,13 +39,15 @@ namespace RainmeterStudio.Tests.Model Assert.AreEqual("root", root.QualifiedName); Assert.AreEqual("D:\\Data\\Project", root.StoragePath); Assert.IsTrue(Enumerable.Repeat("root", 1).SequenceEqual(root.QualifiedParts)); - + Assert.AreEqual(ReferenceTargetKind.Directory, root.TargetKind); + // Test file Assert.AreEqual("f ile__asdf.txt", file.Name); Assert.IsNull(file.Parent); Assert.AreEqual("f ile__asdf.txt", file.QualifiedName); Assert.IsNull(file.StoragePath); Assert.IsTrue(Enumerable.Repeat("f ile__asdf.txt", 1).SequenceEqual(file.QualifiedParts)); + Assert.AreEqual(ReferenceTargetKind.File, file.TargetKind); } /// @@ -45,11 +57,11 @@ namespace RainmeterStudio.Tests.Model public void ReferenceParentingTest() { Reference root = new Reference(String.Empty, "D:\\Data\\Project"); - Reference folder1 = new Reference("folder1", Reference.ReferenceTargetKind.Directory); - Reference folder2 = new Reference("folder 2", Reference.ReferenceTargetKind.Directory); - Reference file1 = new Reference("file1", Reference.ReferenceTargetKind.File); - Reference file2 = new Reference("file2.txt", Reference.ReferenceTargetKind.File); - Reference file3 = new Reference("file 3.png", Reference.ReferenceTargetKind.File); + Reference folder1 = new Reference("folder1", ReferenceTargetKind.Directory); + Reference folder2 = new Reference("folder 2", ReferenceTargetKind.Directory); + Reference file1 = new Reference("file1", ReferenceTargetKind.File); + Reference file2 = new Reference("file2.txt", ReferenceTargetKind.File); + Reference file3 = new Reference("file 3.png", ReferenceTargetKind.File); root.Add(folder1); root.Add(file3); @@ -87,11 +99,11 @@ namespace RainmeterStudio.Tests.Model public void ReferenceQualifiedNameTest() { Reference root = new Reference(String.Empty, "D:\\Data\\Project"); - Reference folder1 = new Reference("folder1", Reference.ReferenceTargetKind.Directory); - Reference folder2 = new Reference("folder 2", Reference.ReferenceTargetKind.Directory); - Reference file1 = new Reference("file1", Reference.ReferenceTargetKind.File); - Reference file2 = new Reference("file2.txt", Reference.ReferenceTargetKind.File); - Reference file3 = new Reference("file 3.png", Reference.ReferenceTargetKind.File); + Reference folder1 = new Reference("folder1", ReferenceTargetKind.Directory); + Reference folder2 = new Reference("folder 2", ReferenceTargetKind.Directory); + Reference file1 = new Reference("file1", ReferenceTargetKind.File); + Reference file2 = new Reference("file2.txt", ReferenceTargetKind.File); + Reference file3 = new Reference("file 3.png", ReferenceTargetKind.File); root.Add(folder1); root.Add(file3); @@ -134,11 +146,12 @@ namespace RainmeterStudio.Tests.Model public void ReferenceGetReferenceTest() { Reference root = new Reference(String.Empty, "D:\\Data\\Project"); - Reference folder1 = new Reference("folder1", Reference.ReferenceTargetKind.Directory); - Reference folder2 = new Reference("folder 2", Reference.ReferenceTargetKind.Directory); - Reference file1 = new Reference("file1", Reference.ReferenceTargetKind.File); - Reference file2 = new Reference("file2.txt", Reference.ReferenceTargetKind.File); - Reference file3 = new Reference("file 3.png", Reference.ReferenceTargetKind.File); + Reference folder1 = new Reference("folder1", ReferenceTargetKind.Directory); + Reference folder2 = new Reference("folder 2", ReferenceTargetKind.Directory); + Reference file1 = new Reference("file1", ReferenceTargetKind.File); + Reference file2 = new Reference("file2.txt", ReferenceTargetKind.File); + Reference file3 = new Reference("file 3.png", ReferenceTargetKind.File); + Reference notInTree = new Reference("file4.txt", ReferenceTargetKind.File); root.Add(folder1); root.Add(file3); @@ -179,6 +192,150 @@ namespace RainmeterStudio.Tests.Model Assert.IsTrue(file3.TryGetReference("/folder1/folder 2/file2.txt", out res)); Assert.AreEqual(file2, res); + + Assert.IsFalse(root.TryGetReference("/file 3.png/some nonexistant file", out res)); + + // Test 'tree contains' method + Assert.IsTrue(file2.TreeContains(root)); + Assert.IsTrue(root.TreeContains(file3)); + Assert.IsFalse(root.TreeContains(notInTree)); + Assert.IsFalse(notInTree.TreeContains(root)); + } + + /// + /// Tests the collection notify behavior + /// + [TestMethod] + public void ReferenceCollectionChangedTest() + { + NotifyCollectionChangedEventArgs args = null; + + // Initialize + Reference root = new Reference(String.Empty, "D:\\Data\\Project"); + Reference folder1 = new Reference("folder1", ReferenceTargetKind.Directory); + Reference file1 = new Reference("file1.txt", ReferenceTargetKind.File); + Reference file2 = new Reference("file2.txt", ReferenceTargetKind.File); + Reference file3 = new Reference("file3.txt", ReferenceTargetKind.File); + + root.CollectionChanged += new NotifyCollectionChangedEventHandler((sender, e) => args = e); + + // Add item + args = null; + root.Add(folder1); + + Assert.IsNotNull(args); + Assert.AreEqual(NotifyCollectionChangedAction.Add, args.Action); + Assert.AreEqual(1, args.NewItems.Count); + Assert.AreEqual(folder1, args.NewItems[0]); + Assert.IsNull(args.OldItems); + + args = null; + folder1.Add(file1); + Assert.IsNull(args); + + args = null; + root.Add(file2); + root.Add(file3); + Assert.IsNotNull(args); + + // Remove + args = null; + root.Remove(file3); + Assert.IsNotNull(root); + Assert.AreEqual(NotifyCollectionChangedAction.Remove, args.Action); + Assert.IsNull(args.NewItems); + Assert.AreEqual(1, args.OldItems.Count); + Assert.AreEqual(file3, args.OldItems[0]); + + // Unparent + args = null; + file2.Unparent(); + Assert.IsNotNull(args); + Assert.AreEqual(NotifyCollectionChangedAction.Remove, args.Action); + Assert.IsNull(args.NewItems); + Assert.AreEqual(1, args.OldItems.Count); + Assert.AreEqual(file2, args.OldItems[0]); + + // Clear + args = null; + root.Clear(); + Assert.IsNotNull(args); + Assert.AreEqual(NotifyCollectionChangedAction.Reset, args.Action); + Assert.IsNull(args.NewItems); + Assert.IsNull(args.OldItems); + } + + /// + /// Tests the property notify behavior + /// + [TestMethod] + public void ReferencePropertyChangedTest() + { + List propNames = new List(); + + // Initialize + Reference root = new Reference("Project", "D:\\Data\\Project", ReferenceTargetKind.Project); + Reference folder1 = new Reference("folder1", ReferenceTargetKind.Directory); + Reference file1 = new Reference("file1.txt", ReferenceTargetKind.File); + + file1.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler((sender, e) => propNames.Add(e.PropertyName)); + + // Parent + propNames.Clear(); + folder1.Add(file1); + Assert.AreEqual(2, propNames.Count); + Assert.IsTrue(propNames.Contains("Parent")); + Assert.IsTrue(propNames.Contains("QualifiedName")); + + // Storage path + propNames.Clear(); + file1.StoragePath = "D:\\Data\\Project\\folder1\\file1.txt"; + Assert.AreEqual(1, propNames.Count); + Assert.AreEqual("StoragePath", propNames[0]); + + // Target kind + propNames.Clear(); + file1.TargetKind = ReferenceTargetKind.None; + Assert.AreEqual(1, propNames.Count); + Assert.AreEqual("TargetKind", propNames[0]); + + // Name + propNames.Clear(); + file1.Name = "file10.txt"; + Assert.AreEqual(2, propNames.Count); + Assert.IsTrue(propNames.Contains("Name")); + Assert.IsTrue(propNames.Contains("QualifiedName")); + + // Qualified name propagation + propNames.Clear(); + root.Add(folder1); + Assert.AreEqual(1, propNames.Count); + Assert.IsTrue(propNames.Contains("QualifiedName")); + } + + /// + /// Tests the automatic detection of target kinds + /// + [TestMethod] + public void ReferenceTargetKindTest() + { + // Create some files and folders + Directory.CreateDirectory("folder1"); + File.Create("file1.txt").Close(); + File.Create("file2.rsproj").Close(); + File.Create("folder1/file3").Close(); + + Reference folder1 = new Reference("folder1", "folder1"); + Reference file1 = new Reference("file1.txt", "file1.txt"); + Reference file2 = new Reference("file2.rsproj", "file2.rsproj"); + Reference file3 = new Reference("file3", "folder1/file3"); + Reference file4 = new Reference("file4", "file4.txt"); + + Assert.AreEqual(ReferenceTargetKind.Directory, folder1.TargetKind); + Assert.AreEqual(ReferenceTargetKind.File, file1.TargetKind); + Assert.AreEqual(ReferenceTargetKind.Project, file2.TargetKind); + Assert.AreEqual(ReferenceTargetKind.File, file3.TargetKind); + Assert.AreEqual(ReferenceTargetKind.None, file4.TargetKind); } } } diff --git a/RainmeterStudio.Tests/RainmeterStudio.Tests.csproj b/RainmeterStudio.Tests/RainmeterStudio.Tests.csproj index c4e6fee6..4eddf2f7 100644 --- a/RainmeterStudio.Tests/RainmeterStudio.Tests.csproj +++ b/RainmeterStudio.Tests/RainmeterStudio.Tests.csproj @@ -57,6 +57,10 @@ + + + + diff --git a/RainmeterStudio.Tests/Storage/ProjectStorageTest.cs b/RainmeterStudio.Tests/Storage/ProjectStorageTest.cs index 56ed1de4..61134b05 100644 --- a/RainmeterStudio.Tests/Storage/ProjectStorageTest.cs +++ b/RainmeterStudio.Tests/Storage/ProjectStorageTest.cs @@ -3,6 +3,7 @@ using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using RainmeterStudio.Core.Model; using RainmeterStudio.Editor.ProjectEditor; +using RainmeterStudio.Storage; using Version = RainmeterStudio.Core.Utils.Version; namespace RainmeterStudio.Tests.Storage @@ -13,8 +14,6 @@ namespace RainmeterStudio.Tests.Storage [TestClass] public class ProjectStorageTest { - private ProjectStorage ProjectStorage = new ProjectStorage(); - public TestContext TestContext { get; set; } [TestInitialize] diff --git a/RainmeterStudio/Business/ProjectManager.cs b/RainmeterStudio/Business/ProjectManager.cs index 29fc26fa..1bdf1320 100644 --- a/RainmeterStudio/Business/ProjectManager.cs +++ b/RainmeterStudio/Business/ProjectManager.cs @@ -3,9 +3,14 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using System.Text.RegularExpressions; +using System.Windows; +using System.Xml.Serialization; using RainmeterStudio.Core.Model; using RainmeterStudio.Core.Storage; +using RainmeterStudio.Core.Utils; using RainmeterStudio.Editor.ProjectEditor; +using RainmeterStudio.Storage; namespace RainmeterStudio.Business { @@ -20,11 +25,6 @@ namespace RainmeterStudio.Business /// public Project ActiveProject { get; protected set; } - /// - /// Gets or sets the project storage - /// - protected ProjectStorage Storage { get; set; } - #endregion #region Events @@ -42,9 +42,8 @@ namespace RainmeterStudio.Business /// Initializes the project manager /// /// Project storage - public ProjectManager(ProjectStorage storage) + public ProjectManager() { - Storage = storage; ActiveProject = null; } @@ -69,7 +68,10 @@ namespace RainmeterStudio.Business ActiveProject.Path = path; // Save to file - Directory.CreateDirectory(Path.GetDirectoryName(path)); + string directory = Path.GetDirectoryName(path); + if (!String.IsNullOrEmpty(directory)) + Directory.CreateDirectory(Path.GetDirectoryName(path)); + SaveActiveProject(); // Raise event @@ -81,14 +83,14 @@ namespace RainmeterStudio.Business /// Opens a project from disk /// /// - public void OpenProject(string path) + public void OpenProject(string path) { // If there is an opened project, close it if (ActiveProject != null) Close(); // Open using storage - ActiveProject = Storage.Read(path); + ActiveProject = ProjectStorage.Read(path); ActiveProject.Path = path; // Raise event @@ -106,13 +108,13 @@ namespace RainmeterStudio.Business throw new InvalidOperationException("Cannot save a project that is not opened."); // Save - Storage.Write(ActiveProject); + ProjectStorage.Write(ActiveProject); } /// /// Closes an opened project /// - public void Close() + public void Close() { ActiveProject = null; @@ -133,12 +135,245 @@ namespace RainmeterStudio.Business { _projectTemplates.Add(template); } - + /// /// Gets a list of existing project templates /// public IEnumerable ProjectTemplates { get { return _projectTemplates; } } #endregion + + #region Project item operations + + [Serializable] + protected struct ClipboardData + { + public bool Cut; + public string QualifiedName; + } + + /// + /// Places a project item in the clipboard, and marks it for deletion + /// + /// Project item to cut + public void ProjectItemCutClipboard(Reference @ref) + { + var dataFormat = DataFormats.GetDataFormat(typeof(ClipboardData).FullName); + + ClipboardData data = new ClipboardData(); + data.Cut = true; + data.QualifiedName = @ref.QualifiedName; + + Clipboard.SetData(dataFormat.Name, data); + } + + /// + /// Places a project item in the clipboard + /// + /// Project item to copy + public void ProjectItemCopyClipboard(Reference @ref) + { + var dataFormat = DataFormats.GetDataFormat(typeof(ClipboardData).FullName); + + ClipboardData data = new ClipboardData(); + data.Cut = false; + data.QualifiedName = @ref.QualifiedName; + + Clipboard.SetData(dataFormat.Name, data); + } + + /// + /// Pastes a project item from clipboard + /// + /// Destination + public void ProjectItemPasteClipboard(Reference dest) + { + var dataFormat = DataFormats.GetDataFormat(typeof(ClipboardData).FullName); + + if (Clipboard.ContainsData(dataFormat.Name)) + { + ClipboardData data = (ClipboardData)Clipboard.GetData(dataFormat.Name); + var reference = ActiveProject.Root.GetReference(data.QualifiedName); + + if (data.Cut) + { + ProjectItemMove(reference, dest); + Clipboard.Clear(); + } + else + { + ProjectItemCopy(reference, dest); + } + } + } + + /// + /// Moves a project item to another folder + /// + /// Project item to move + /// Destination folder + public void ProjectItemMove(Reference @ref, Reference dest) + { + // Move storage file + string refPath = Path.GetFileName(@ref.StoragePath.TrimEnd('\\')); + string destinationPath = (dest.TargetKind == ReferenceTargetKind.Directory) ? dest.StoragePath : Path.GetDirectoryName(dest.StoragePath); + string newPath = Path.Combine(destinationPath, refPath); + + if (@ref.TargetKind == ReferenceTargetKind.Directory) + { + Directory.Move(@ref.StoragePath, newPath); + + // Update children + UpdateRenameChildren(@ref, @ref.StoragePath, newPath); + } + else + { + File.Move(@ref.StoragePath, newPath); + } + + // Set up reference object + @ref.Unparent(); + @ref.StoragePath = newPath; + dest.Add(@ref); + } + + private void UpdateRenameChildren(Reference root, string oldPath, string newPath) + { + foreach (var pair in root.ChildrenDictionary) + { + pair.Value.StoragePath = pair.Value.StoragePath.Replace(oldPath, newPath); + UpdateRenameChildren(pair.Value, oldPath, newPath); + } + } + + /// + /// Creates a copy of a project item to another folder + /// + /// Project item to copy + /// Destination folder + /// Reference to the copy + public Reference ProjectItemCopy(Reference @ref, Reference dest) + { + // Create a clone reference + var copyRef = (Reference)@ref.Clone(); + + // Copy storage file + string refPath = Path.GetFileName(@ref.StoragePath.TrimEnd('\\')); + string destinationPath = (dest.TargetKind == ReferenceTargetKind.Directory) ? dest.StoragePath : Path.GetDirectoryName(dest.StoragePath); + string newPath = Path.Combine(destinationPath, refPath); + + if (@ref.TargetKind == ReferenceTargetKind.Directory) + { + DirectoryHelper.CopyDirectory(@ref.StoragePath, newPath); + + // Update children + UpdateRenameChildren(copyRef, copyRef.StoragePath, newPath); + } + else + { + // Find a nonconflicting file name + newPath = GetNonConflictingPath(refPath, destinationPath); + + // Copy + File.Copy(@ref.StoragePath, newPath); + } + + // Parent reference + copyRef.Name = Path.GetFileName(newPath); + copyRef.StoragePath = newPath; + dest.Add(copyRef); + return copyRef; + } + + private static string GetNonConflictingPath(string filename, string destinationPath) + { + // Initial path - destination path + file name + string newPath = Path.Combine(destinationPath, filename); + + // Initial number + int i = 1; + + // Try to find if there already is a number + var match = Regex.Match(newPath, "_([0-9])$"); + if (match.Success) + { + i = Int32.Parse(match.Groups[1].Value); + } + + // Find non-conflicting number + while (File.Exists(newPath)) + { + ++i; + newPath = Path.Combine(destinationPath, Path.GetFileNameWithoutExtension(filename) + "_" + i.ToString() + Path.GetExtension(filename)); + } + + return newPath; + } + + public void ProjectItemRename(Reference @ref, string newName) + { + // Rename on disk + string refPath = @ref.StoragePath.TrimEnd('\\'); + string refDir = Path.GetDirectoryName(refPath); + string newPath = Path.Combine(refDir, newName); + + if (@ref.TargetKind == ReferenceTargetKind.Directory) + { + Directory.Move(refPath, newPath); + newPath += '\\'; + } + else + { + File.Move(refPath, newPath); + } + + // Set reference + @ref.Name = newName; + @ref.StoragePath = newPath; + } + + /// + /// Deletes a project item + /// + /// + public void ProjectItemDelete(Reference @ref, bool fromDisk) + { + if (fromDisk) + { + if (@ref.TargetKind == ReferenceTargetKind.File) + File.Delete(@ref.StoragePath); + + else Directory.Delete(@ref.StoragePath, true); + } + + @ref.Unparent(); + } + + /// + /// Checks if there is a project item in the clipboard + /// + /// True if there is a project item in the clipboard + public bool HaveProjectItemInClipboard() + { + var dataFormat = DataFormats.GetDataFormat(typeof(ClipboardData).FullName); + return Clipboard.ContainsData(dataFormat.Name); + } + + /// + /// Creates a new folder with given name + /// + /// Name of folder + /// Parent folder + public void CreateFolder(string name, Reference parent) + { + string dir = (parent.TargetKind == ReferenceTargetKind.Directory) ? + parent.StoragePath : Path.GetDirectoryName(parent.StoragePath); + string newDirPath = Path.Combine(dir, name); + + Directory.CreateDirectory(newDirPath); + parent.Add(new Reference(name, newDirPath, ReferenceTargetKind.Directory)); + } + + #endregion } } diff --git a/RainmeterStudio/Editor/ProjectEditor/ProjectStorage.cs b/RainmeterStudio/Editor/ProjectEditor/ProjectDocumentStorage.cs similarity index 51% rename from RainmeterStudio/Editor/ProjectEditor/ProjectStorage.cs rename to RainmeterStudio/Editor/ProjectEditor/ProjectDocumentStorage.cs index 8c8704a3..e56781a1 100644 --- a/RainmeterStudio/Editor/ProjectEditor/ProjectStorage.cs +++ b/RainmeterStudio/Editor/ProjectEditor/ProjectDocumentStorage.cs @@ -7,6 +7,7 @@ using System.Xml.Serialization; using RainmeterStudio.Core; using RainmeterStudio.Core.Model; using RainmeterStudio.Core.Storage; +using RainmeterStudio.Storage; namespace RainmeterStudio.Editor.ProjectEditor { @@ -14,60 +15,8 @@ namespace RainmeterStudio.Editor.ProjectEditor /// Project storage, loads and saves project files /// [PluginExport] - public class ProjectStorage : IDocumentStorage + public class ProjectDocumentStorage : IDocumentStorage { - /// - /// Loads a project from file - /// - /// Path to file to load - /// Loaded project - public Project Read(string path) - { - // Open file - var file = File.OpenText(path); - - // Deserialize file - var serializer = new XmlSerializer(typeof(Project), new XmlRootAttribute("project")); - Project project = serializer.Deserialize(file) as Project; - - if (project != null) - { - project.Path = path; - } - - // Clean up - file.Close(); - return project; - } - - /// - /// Saves a project to file - /// - /// Project to save - /// File to save to - public void Write(Project project, string path) - { - // Open file - var file = File.OpenWrite(path); - - // Serialize file - var serializer = new XmlSerializer(typeof(Project), new XmlRootAttribute("project")); - serializer.Serialize(file, project); - - // Clean up - file.Close(); - project.Path = path; - } - - /// - /// Saves a project - /// - /// Saves a project to the path specified in the 'Path' property - public void Write(Project project) - { - Write(project, project.Path); - } - /// /// Reads the project as a ProjectDocument. /// Use Load to get only the Project. @@ -76,9 +25,9 @@ namespace RainmeterStudio.Editor.ProjectEditor /// A project document public IDocument ReadDocument(string path) { - Project project = Read(path); + Project project = ProjectStorage.Read(path); var document = new ProjectDocument(project); - document.Reference = new Reference(Path.GetFileName(path), path); + document.Reference = new Reference(Path.GetFileName(path), path, ReferenceTargetKind.Project); return document; } @@ -91,7 +40,7 @@ namespace RainmeterStudio.Editor.ProjectEditor public void WriteDocument(IDocument document, string path) { var projectDocument = (ProjectDocument)document; - Write(projectDocument.Project, path); + ProjectStorage.Write(projectDocument.Project, path); } /// diff --git a/RainmeterStudio/MainClass.cs b/RainmeterStudio/MainClass.cs index 50a4fb37..cc56ef04 100644 --- a/RainmeterStudio/MainClass.cs +++ b/RainmeterStudio/MainClass.cs @@ -23,11 +23,8 @@ namespace RainmeterStudio SplashScreen splash = new SplashScreen("Resources/splash.png"); splash.Show(true); - // Initialize project manager - ProjectStorage projectStorage = new ProjectStorage(); - ProjectManager projectManager = new ProjectManager(projectStorage); - - // Initialize document manager + // Initialize project, document manager + ProjectManager projectManager = new ProjectManager(); DocumentManager documentManager = new DocumentManager(); // Initialize plugin manager diff --git a/RainmeterStudio/RainmeterStudio.csproj b/RainmeterStudio/RainmeterStudio.csproj index bfe10553..ccbf45aa 100644 --- a/RainmeterStudio/RainmeterStudio.csproj +++ b/RainmeterStudio/RainmeterStudio.csproj @@ -96,14 +96,12 @@ True Strings.resx - + + - - - CloseUnsavedDialog.xaml @@ -114,6 +112,9 @@ CreateProjectDialog.xaml + + InputDialog.xaml + ProjectPanel.xaml @@ -133,6 +134,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + MSBuild:Compile Designer @@ -260,9 +265,7 @@ - - - + + diff --git a/RainmeterStudio/UI/Panels/ProjectPanel.xaml.cs b/RainmeterStudio/UI/Panels/ProjectPanel.xaml.cs index 311107e4..294bb1dc 100644 --- a/RainmeterStudio/UI/Panels/ProjectPanel.xaml.cs +++ b/RainmeterStudio/UI/Panels/ProjectPanel.xaml.cs @@ -1,6 +1,9 @@ using System; +using System.Collections.Generic; using System.Windows; using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; using RainmeterStudio.Core.Model; using RainmeterStudio.Core.Utils; using RainmeterStudio.UI.Controller; @@ -13,28 +16,31 @@ namespace RainmeterStudio.UI.Panels /// public partial class ProjectPanel : UserControl { - private ProjectController _controller; - public ProjectController Controller + private ProjectController _projectController; + + public ProjectController ProjectController { get { - return _controller; + return _projectController; } set { // Unsubscribe from old controller - if (_controller != null) + if (_projectController != null) { - Controller.ActiveProjectChanged -= Controller_ActiveProjectChanged; + ProjectController.ActiveProjectChanged -= Controller_ActiveProjectChanged; } // Set new project - _controller = value; - _controller.ActiveProjectChanged += Controller_ActiveProjectChanged; + _projectController = value; + _projectController.ActiveProjectChanged += Controller_ActiveProjectChanged; Refresh(); } } + public DocumentController DocumentController { get; set; } + #region Commands public Command SyncWithActiveViewCommand { get; private set; } @@ -60,7 +66,7 @@ namespace RainmeterStudio.UI.Panels if (selected == null) { - return Controller.ActiveProject.Root; + return ProjectController.ActiveProject.Root; } else { @@ -85,7 +91,6 @@ namespace RainmeterStudio.UI.Panels } } - public ProjectPanel() { InitializeComponent(); @@ -116,7 +121,7 @@ namespace RainmeterStudio.UI.Panels treeProjectItems.Items.Clear(); // No project - if (Controller == null || Controller.ActiveProject == null) + if (ProjectController == null || ProjectController.ActiveProject == null) { this.IsEnabled = false; } @@ -130,14 +135,14 @@ namespace RainmeterStudio.UI.Panels if (toggleShowAllFiles.IsChecked.HasValue && toggleShowAllFiles.IsChecked.Value) { // Get directory name - string projectFolder = System.IO.Path.GetDirectoryName(Controller.ActiveProjectPath); + string projectFolder = System.IO.Path.GetDirectoryName(ProjectController.ActiveProjectPath); // Get folder tree refTree = DirectoryHelper.GetFolderTree(projectFolder); } else { - refTree = Controller.ActiveProject.Root; + refTree = ProjectController.ActiveProject.Root; } // Add tree to tree view @@ -183,5 +188,106 @@ namespace RainmeterStudio.UI.Panels // We can expand if the root is not expanded CanExpand = (!tree.IsExpanded); } + + private void TreeViewItem_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e) + { + var treeViewItem = sender as TreeViewItem; + var referenceViewModel = treeViewItem.Header as ReferenceViewModel; + + if (referenceViewModel != null) + { + treeViewItem.ContextMenu = new ContextMenu(); + treeViewItem.ContextMenu.ItemsSource = GetContextMenuItems(referenceViewModel.Reference); + } + } + + private void TreeViewItem_PreviewMouseDoubleClick(object sender, MouseButtonEventArgs e) + { + var treeViewItem = sender as TreeViewItem; + var refViewModel = treeViewItem.Header as ReferenceViewModel; + + if (refViewModel != null) + { + Command command = GetDefaultCommand(refViewModel.Reference); + command.Execute(refViewModel.Reference); + } + } + + /// + /// Gets the default command (double click) for a specific reference + /// + /// The reference + /// The command + public Command GetDefaultCommand(Reference reference) + { + switch (reference.TargetKind) + { + case ReferenceTargetKind.File: + return DocumentController.DocumentOpenCommand; + + case ReferenceTargetKind.Project: + return DocumentController.DocumentOpenCommand; + + case ReferenceTargetKind.Directory: + return null; // TODO: expand command + + default: + return null; + } + } + + private MenuItem GetMenuItem(Command cmd, Reference reference) + { + var icon = new Image(); + icon.Source = cmd.Icon; + icon.Width = 16; + icon.Height = 16; + + var menuItem = new MenuItem(); + menuItem.DataContext = cmd; + menuItem.Style = Application.Current.TryFindResource("CommandContextMenuItemStyle") as Style; + menuItem.Icon = icon; + menuItem.CommandParameter = reference; + + if (GetDefaultCommand(reference) == cmd) + menuItem.FontWeight = FontWeights.Bold; + + return menuItem; + } + + public IEnumerable GetContextMenuItems(Reference @ref) + { + if (@ref.TargetKind == ReferenceTargetKind.File || @ref.TargetKind == ReferenceTargetKind.Project) + { + yield return GetMenuItem(DocumentController.DocumentOpenCommand, @ref); + } + if (@ref.TargetKind == ReferenceTargetKind.Directory || @ref.TargetKind == ReferenceTargetKind.Project) + { + // TODO: expand command + } + + yield return new Separator(); + + if (@ref.TargetKind != ReferenceTargetKind.Project) + { + yield return GetMenuItem(ProjectController.ProjectItemCutCommand, @ref); + yield return GetMenuItem(ProjectController.ProjectItemCopyCommand, @ref); + + if (@ref.TargetKind == ReferenceTargetKind.Directory) + yield return GetMenuItem(ProjectController.ProjectItemPasteCommand, @ref); + } + + yield return GetMenuItem(ProjectController.ProjectItemRenameCommand, @ref); + + if (@ref.TargetKind != ReferenceTargetKind.Project) + yield return GetMenuItem(ProjectController.ProjectItemDeleteCommand, @ref); + + yield return new Separator(); + + if (@ref.TargetKind == ReferenceTargetKind.Directory) + yield return GetMenuItem(ProjectController.ProjectItemOpenInExplorerCommand, @ref); + else + yield return GetMenuItem(ProjectController.ProjectItemOpenContainingFolderCommand, @ref); + } } }