Added project item commands, work on project manager, implemented project manager tests

This commit is contained in:
Tiberiu Chibici 2014-09-16 21:57:15 +03:00
parent 425d7d62f1
commit 7691a3c326
30 changed files with 2526 additions and 321 deletions

View File

@ -223,7 +223,7 @@ namespace RainmeterStudio.Core.Model
/// </summary> /// </summary>
public Project() public Project()
{ {
Root = new Reference(String.Empty, Reference.ReferenceTargetKind.Project); Root = new Reference(String.Empty, ReferenceTargetKind.Project);
VariableFiles = new ObservableCollection<Reference>(); VariableFiles = new ObservableCollection<Reference>();
Version = new Version(); Version = new Version();
MinimumRainmeter = new Version("3.1"); MinimumRainmeter = new Version("3.1");

View File

@ -11,12 +11,6 @@ using System.Xml.Serialization;
using RainmeterStudio.Core.Utils; using RainmeterStudio.Core.Utils;
namespace RainmeterStudio.Core.Model namespace RainmeterStudio.Core.Model
{
/// <summary>
/// Reference to a file or folder
/// </summary>
[DebuggerDisplay("QualifiedName = {QualifiedName}, StoragePath = {StoragePath}")]
public class Reference : INotifyCollectionChanged, INotifyPropertyChanged
{ {
/// <summary> /// <summary>
/// The kind of item the reference points to /// The kind of item the reference points to
@ -44,6 +38,12 @@ namespace RainmeterStudio.Core.Model
Project Project
} }
/// <summary>
/// Reference to a file or folder
/// </summary>
[DebuggerDisplay("QualifiedName = {QualifiedName}, StoragePath = {StoragePath}")]
public class Reference : INotifyCollectionChanged, INotifyPropertyChanged, ICloneable
{
private Dictionary<string, Reference> _children; private Dictionary<string, Reference> _children;
private Reference _parent; private Reference _parent;
private string _name, _storagePath; private string _name, _storagePath;
@ -90,7 +90,10 @@ namespace RainmeterStudio.Core.Model
// Notify // Notify
if (PropertyChanged != null) if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Parent")); PropertyChanged(this, new PropertyChangedEventArgs("Parent"));
PropertyChanged(this, new PropertyChangedEventArgs("QualifiedName"));
}
} }
} }
@ -138,7 +141,10 @@ namespace RainmeterStudio.Core.Model
_name = value; _name = value;
if (PropertyChanged != null) if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Name")); PropertyChanged(this, new PropertyChangedEventArgs("Name"));
PropertyChanged(this, new PropertyChangedEventArgs("QualifiedName"));
}
} }
} }
@ -216,7 +222,7 @@ namespace RainmeterStudio.Core.Model
_kind = value; _kind = value;
if (PropertyChanged != null) 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) 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")); PropertyChanged(this, new PropertyChangedEventArgs("QualifiedName"));
} }
@ -437,6 +443,23 @@ namespace RainmeterStudio.Core.Model
} }
#endregion #endregion
/// <summary>
/// Creates a clone of this reference
/// </summary>
/// <returns>The clone</returns>
/// <remarks>The clone doesn't keep the parent.</remarks>
public object Clone()
{
var cloneReference = new Reference(Name, StoragePath, TargetKind);
foreach (var r in Children)
{
cloneReference.Add((Reference)r.Clone());
}
return cloneReference;
}
} }
/// <summary> /// <summary>

View File

@ -159,4 +159,158 @@ namespace RainmeterStudio.Core.Model
throw new NotImplementedException(); throw new NotImplementedException();
} }
} }
/// <summary>
/// Extension methods for trees
/// </summary>
public static class TreeExtensions
{
/// <summary>
/// Tree traversal orders
/// </summary>
public enum TreeTraversalOrder
{
BreadthFirst,
DepthFirst,
DepthFirstPreOrder = DepthFirst,
DepthFirstPostOrder
}
/// <summary>
/// Traverses a tree
/// </summary>
/// <typeparam name="T">Tree data type</typeparam>
/// <param name="root">Root node of tree</param>
/// <param name="order">Traversal order</param>
/// <returns>An enumeration of the nodes in the specified traverse order</returns>
public static IEnumerable<Tree<T>> Traverse<T>(this Tree<T> root, TreeTraversalOrder order = TreeTraversalOrder.BreadthFirst)
{
if (order == TreeTraversalOrder.BreadthFirst)
return TraverseBF(root);
else return TraverseDF(root, order);
}
private static IEnumerable<Tree<T>> TraverseDF<T>(this Tree<T> 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<Tree<T>> TraverseBF<T>(this Tree<T> root)
{
// Create a queue containing the root
Queue<Tree<T>> queue = new Queue<Tree<T>>();
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);
}
}
/// <summary>
/// Applies an action to every node of the tree
/// </summary>
/// <typeparam name="T">Tree data type</typeparam>
/// <param name="root">Root node of tree</param>
/// <param name="action">Action to apply</param>
/// <param name="order">Traversal order</param>
public static void Apply<T>(this Tree<T> root, Action<Tree<T>> action, TreeTraversalOrder order = TreeTraversalOrder.BreadthFirst)
{
// Safety check
if (action == null)
return;
// Apply action
foreach (var node in Traverse(root, order))
action(node);
}
/// <summary>
/// Applies an action to every node of the tree
/// </summary>
/// <typeparam name="T">Tree data type</typeparam>
/// <param name="root">Root node of tree</param>
/// <param name="action">Action to apply</param>
/// <param name="order">Traversal order</param>
public static void ApplyToData<T>(this Tree<T> root, Action<T> 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);
}
/// <summary>
/// Rebuilds the tree by applying the specified transform function
/// </summary>
/// <typeparam name="T">Data type of tree</typeparam>
/// <typeparam name="TResult">Data type of rebuilt tree</typeparam>
/// <param name="root">The root node</param>
/// <param name="transformFunction">The transform function</param>
/// <returns>The transformed tree</returns>
public static Tree<TResult> Transform<T, TResult>(this Tree<T> root, Func<Tree<T>, Tree<TResult>> transformFunction)
{
// Safety check
if (transformFunction == null)
throw new ArgumentNullException("Transform function cannot be null.");
// Build root
Tree<TResult> resRoot = transformFunction(root);
// Add children
foreach (var node in root.Children)
resRoot.Children.Add(Transform(node, transformFunction));
// Return
return resRoot;
}
/// <summary>
/// Rebuilds the tree by applying the specified transform function
/// </summary>
/// <typeparam name="T">Data type of tree</typeparam>
/// <typeparam name="TResult">Data type of rebuilt tree</typeparam>
/// <param name="root">The root node</param>
/// <param name="transformFunction">The transform function</param>
/// <returns>The transformed tree</returns>
public static Tree<TResult> TransformData<T, TResult>(this Tree<T> root, Func<T, TResult> transformFunction)
{
// Safety check
if (transformFunction == null)
throw new ArgumentNullException("Transform function cannot be null.");
// Build root
Tree<TResult> resRoot = new Tree<TResult>(transformFunction(root.Data));
// Add children
foreach (var node in root.Children)
resRoot.Children.Add(TransformData(node, transformFunction));
// Return
return resRoot;
}
}
} }

View File

@ -71,7 +71,6 @@
<Compile Include="Utils\LinqExtension.cs" /> <Compile Include="Utils\LinqExtension.cs" />
<Compile Include="Utils\BitmapHelper.cs" /> <Compile Include="Utils\BitmapHelper.cs" />
<Compile Include="Utils\PathHelper.cs" /> <Compile Include="Utils\PathHelper.cs" />
<Compile Include="Utils\TreeExtensions.cs" />
<Compile Include="Utils\Version.cs" /> <Compile Include="Utils\Version.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

View File

@ -17,11 +17,13 @@ namespace RainmeterStudio.Core.Utils
public static Reference GetFolderTree(string folder) public static Reference GetFolderTree(string folder)
{ {
// Build tree object // Build tree object
Reference refTree = new Reference(Path.GetFileName(folder), folder); Reference refTree = new Reference(Path.GetFileName(folder), folder, ReferenceTargetKind.File);
// Navigate folder structure // Navigate folder structure
if (Directory.Exists(folder)) if (Directory.Exists(folder))
{ {
refTree.TargetKind = ReferenceTargetKind.Directory;
foreach (var item in Directory.EnumerateDirectories(folder) foreach (var item in Directory.EnumerateDirectories(folder)
.Concat(Directory.EnumerateFiles(folder))) .Concat(Directory.EnumerateFiles(folder)))
{ {
@ -46,5 +48,33 @@ namespace RainmeterStudio.Core.Utils
return String.Equals(path1, path2, StringComparison.InvariantCultureIgnoreCase); return String.Equals(path1, path2, StringComparison.InvariantCultureIgnoreCase);
} }
/// <summary>
/// Copies a directory from source to destination
/// </summary>
/// <param name="source">Directory to copy</param>
/// <param name="destination">Destination directory</param>
/// <param name="merge"></param>
/// <remarks>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.</remarks>
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);
}
}
} }
} }

View File

@ -1,162 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RainmeterStudio.Core.Model;
namespace RainmeterStudio.Core.Utils
{
/// <summary>
/// Extension methods for trees
/// </summary>
public static class TreeExtensions
{
/// <summary>
/// Tree traversal orders
/// </summary>
public enum TreeTraversalOrder
{
BreadthFirst,
DepthFirst,
DepthFirstPreOrder = DepthFirst,
DepthFirstPostOrder
}
/// <summary>
/// Traverses a tree
/// </summary>
/// <typeparam name="T">Tree data type</typeparam>
/// <param name="root">Root node of tree</param>
/// <param name="order">Traversal order</param>
/// <returns>An enumeration of the nodes in the specified traverse order</returns>
public static IEnumerable<Tree<T>> Traverse<T>(this Tree<T> root, TreeTraversalOrder order = TreeTraversalOrder.BreadthFirst)
{
if (order == TreeTraversalOrder.BreadthFirst)
return TraverseBF(root);
else return TraverseDF(root, order);
}
private static IEnumerable<Tree<T>> TraverseDF<T>(this Tree<T> 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<Tree<T>> TraverseBF<T>(this Tree<T> root)
{
// Create a queue containing the root
Queue<Tree<T>> queue = new Queue<Tree<T>>();
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);
}
}
/// <summary>
/// Applies an action to every node of the tree
/// </summary>
/// <typeparam name="T">Tree data type</typeparam>
/// <param name="root">Root node of tree</param>
/// <param name="action">Action to apply</param>
/// <param name="order">Traversal order</param>
public static void Apply<T>(this Tree<T> root, Action<Tree<T>> action, TreeTraversalOrder order = TreeTraversalOrder.BreadthFirst)
{
// Safety check
if (action == null)
return;
// Apply action
foreach (var node in Traverse(root, order))
action(node);
}
/// <summary>
/// Applies an action to every node of the tree
/// </summary>
/// <typeparam name="T">Tree data type</typeparam>
/// <param name="root">Root node of tree</param>
/// <param name="action">Action to apply</param>
/// <param name="order">Traversal order</param>
public static void ApplyToData<T>(this Tree<T> root, Action<T> 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);
}
/// <summary>
/// Rebuilds the tree by applying the specified transform function
/// </summary>
/// <typeparam name="T">Data type of tree</typeparam>
/// <typeparam name="TResult">Data type of rebuilt tree</typeparam>
/// <param name="root">The root node</param>
/// <param name="transformFunction">The transform function</param>
/// <returns>The transformed tree</returns>
public static Tree<TResult> Transform<T,TResult>(this Tree<T> root, Func<Tree<T>, Tree<TResult>> transformFunction)
{
// Safety check
if (transformFunction == null)
throw new ArgumentNullException("Transform function cannot be null.");
// Build root
Tree<TResult> resRoot = transformFunction(root);
// Add children
foreach (var node in root.Children)
resRoot.Children.Add(Transform(node, transformFunction));
// Return
return resRoot;
}
/// <summary>
/// Rebuilds the tree by applying the specified transform function
/// </summary>
/// <typeparam name="T">Data type of tree</typeparam>
/// <typeparam name="TResult">Data type of rebuilt tree</typeparam>
/// <param name="root">The root node</param>
/// <param name="transformFunction">The transform function</param>
/// <returns>The transformed tree</returns>
public static Tree<TResult> TransformData<T, TResult>(this Tree<T> root, Func<T, TResult> transformFunction)
{
// Safety check
if (transformFunction == null)
throw new ArgumentNullException("Transform function cannot be null.");
// Build root
Tree<TResult> resRoot = new Tree<TResult>(transformFunction(root.Data));
// Add children
foreach (var node in root.Children)
resRoot.Children.Add(TransformData(node, transformFunction));
// Return
return resRoot;
}
}
}

View File

@ -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();
}
}
}

View File

@ -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
{
/// <summary>
/// Tests the project item operations for the project manager
/// </summary>
/// <remarks>
/// Initial project structure:
/// root
/// + folder1
/// | + sub1
/// | | + file2.png
/// | | + file3.txt
/// | + file1.txt
/// + folder2
/// + file1.txt
/// + sub1
/// </remarks>
[TestClass]
public class ProjectManagerProjectItemOperationsTest : ProjectManagerTestBase
{
private Reference Root
{
get
{
return ProjectManager.ActiveProject.Root;
}
}
/// <summary>
/// Sets up test
/// </summary>
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
}
}

View File

@ -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
{
/// <summary>
/// Tests if the sample template is registered
/// </summary>
[TestMethod]
public void ProjectManagerTemplatesTest()
{
Assert.AreEqual(1, ProjectManager.ProjectTemplates.Count());
Assert.AreEqual(ProjectTemplate, ProjectManager.ProjectTemplates.First());
}
/// <summary>
/// Tests the project create functionality
/// </summary>
[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));
}
/// <summary>
/// Tests the open project functionality
/// </summary>
[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);
}
}
}

View File

@ -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
{
/// <summary>
/// Common stuff for project manager tests
/// </summary>
public class ProjectManagerTestBase
{
#region Project template
/// <summary>
/// A sample project template
/// </summary>
protected class TestTemplate : IProjectTemplate
{
public string Name
{
get { return "TestTemplate"; }
}
public IEnumerable<Property> Properties
{
get { return Enumerable.Empty<Property>(); }
}
public Project CreateProject()
{
return new Project();
}
}
#endregion
/// <summary>
/// Gets or sets the text context
/// </summary>
public TestContext TestContext { get; set; }
/// <summary>
/// Gets or sets the project manager
/// </summary>
protected virtual ProjectManager ProjectManager { get; set; }
/// <summary>
/// Gets or sets the sample project template
/// </summary>
protected virtual IProjectTemplate ProjectTemplate { get; set; }
/// <summary>
/// Sets up the test
/// </summary>
[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()
{
}
}
}

View File

@ -1,5 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -14,14 +16,22 @@ namespace RainmeterStudio.Tests.Model
[TestClass] [TestClass]
public class ReferenceTest public class ReferenceTest
{ {
public TestContext TestContext { get; set; }
[TestInitialize]
public void Initialize()
{
Directory.SetCurrentDirectory(TestContext.DeploymentDirectory);
}
/// <summary> /// <summary>
/// Tests the constructors of the reference class /// Tests the constructors of the reference class
/// </summary> /// </summary>
[TestMethod] [TestMethod]
public void ReferenceConstructorTest() public void ReferenceConstructorTest()
{ {
Reference root = new Reference("root", "D:\\Data\\Project", Reference.ReferenceTargetKind.Directory); Reference root = new Reference("root", "D:\\Data\\Project", ReferenceTargetKind.Directory);
Reference file = new Reference("f ile__asdf.txt", Reference.ReferenceTargetKind.File); Reference file = new Reference("f ile__asdf.txt", ReferenceTargetKind.File);
// Test root // Test root
Assert.AreEqual("root", root.Name); Assert.AreEqual("root", root.Name);
@ -29,6 +39,7 @@ namespace RainmeterStudio.Tests.Model
Assert.AreEqual("root", root.QualifiedName); Assert.AreEqual("root", root.QualifiedName);
Assert.AreEqual("D:\\Data\\Project", root.StoragePath); Assert.AreEqual("D:\\Data\\Project", root.StoragePath);
Assert.IsTrue(Enumerable.Repeat("root", 1).SequenceEqual(root.QualifiedParts)); Assert.IsTrue(Enumerable.Repeat("root", 1).SequenceEqual(root.QualifiedParts));
Assert.AreEqual(ReferenceTargetKind.Directory, root.TargetKind);
// Test file // Test file
Assert.AreEqual("f ile__asdf.txt", file.Name); Assert.AreEqual("f ile__asdf.txt", file.Name);
@ -36,6 +47,7 @@ namespace RainmeterStudio.Tests.Model
Assert.AreEqual("f ile__asdf.txt", file.QualifiedName); Assert.AreEqual("f ile__asdf.txt", file.QualifiedName);
Assert.IsNull(file.StoragePath); Assert.IsNull(file.StoragePath);
Assert.IsTrue(Enumerable.Repeat("f ile__asdf.txt", 1).SequenceEqual(file.QualifiedParts)); Assert.IsTrue(Enumerable.Repeat("f ile__asdf.txt", 1).SequenceEqual(file.QualifiedParts));
Assert.AreEqual(ReferenceTargetKind.File, file.TargetKind);
} }
/// <summary> /// <summary>
@ -45,11 +57,11 @@ namespace RainmeterStudio.Tests.Model
public void ReferenceParentingTest() public void ReferenceParentingTest()
{ {
Reference root = new Reference(String.Empty, "D:\\Data\\Project"); Reference root = new Reference(String.Empty, "D:\\Data\\Project");
Reference folder1 = new Reference("folder1", Reference.ReferenceTargetKind.Directory); Reference folder1 = new Reference("folder1", ReferenceTargetKind.Directory);
Reference folder2 = new Reference("folder 2", Reference.ReferenceTargetKind.Directory); Reference folder2 = new Reference("folder 2", ReferenceTargetKind.Directory);
Reference file1 = new Reference("file1", Reference.ReferenceTargetKind.File); Reference file1 = new Reference("file1", ReferenceTargetKind.File);
Reference file2 = new Reference("file2.txt", Reference.ReferenceTargetKind.File); Reference file2 = new Reference("file2.txt", ReferenceTargetKind.File);
Reference file3 = new Reference("file 3.png", Reference.ReferenceTargetKind.File); Reference file3 = new Reference("file 3.png", ReferenceTargetKind.File);
root.Add(folder1); root.Add(folder1);
root.Add(file3); root.Add(file3);
@ -87,11 +99,11 @@ namespace RainmeterStudio.Tests.Model
public void ReferenceQualifiedNameTest() public void ReferenceQualifiedNameTest()
{ {
Reference root = new Reference(String.Empty, "D:\\Data\\Project"); Reference root = new Reference(String.Empty, "D:\\Data\\Project");
Reference folder1 = new Reference("folder1", Reference.ReferenceTargetKind.Directory); Reference folder1 = new Reference("folder1", ReferenceTargetKind.Directory);
Reference folder2 = new Reference("folder 2", Reference.ReferenceTargetKind.Directory); Reference folder2 = new Reference("folder 2", ReferenceTargetKind.Directory);
Reference file1 = new Reference("file1", Reference.ReferenceTargetKind.File); Reference file1 = new Reference("file1", ReferenceTargetKind.File);
Reference file2 = new Reference("file2.txt", Reference.ReferenceTargetKind.File); Reference file2 = new Reference("file2.txt", ReferenceTargetKind.File);
Reference file3 = new Reference("file 3.png", Reference.ReferenceTargetKind.File); Reference file3 = new Reference("file 3.png", ReferenceTargetKind.File);
root.Add(folder1); root.Add(folder1);
root.Add(file3); root.Add(file3);
@ -134,11 +146,12 @@ namespace RainmeterStudio.Tests.Model
public void ReferenceGetReferenceTest() public void ReferenceGetReferenceTest()
{ {
Reference root = new Reference(String.Empty, "D:\\Data\\Project"); Reference root = new Reference(String.Empty, "D:\\Data\\Project");
Reference folder1 = new Reference("folder1", Reference.ReferenceTargetKind.Directory); Reference folder1 = new Reference("folder1", ReferenceTargetKind.Directory);
Reference folder2 = new Reference("folder 2", Reference.ReferenceTargetKind.Directory); Reference folder2 = new Reference("folder 2", ReferenceTargetKind.Directory);
Reference file1 = new Reference("file1", Reference.ReferenceTargetKind.File); Reference file1 = new Reference("file1", ReferenceTargetKind.File);
Reference file2 = new Reference("file2.txt", Reference.ReferenceTargetKind.File); Reference file2 = new Reference("file2.txt", ReferenceTargetKind.File);
Reference file3 = new Reference("file 3.png", Reference.ReferenceTargetKind.File); Reference file3 = new Reference("file 3.png", ReferenceTargetKind.File);
Reference notInTree = new Reference("file4.txt", ReferenceTargetKind.File);
root.Add(folder1); root.Add(folder1);
root.Add(file3); root.Add(file3);
@ -179,6 +192,150 @@ namespace RainmeterStudio.Tests.Model
Assert.IsTrue(file3.TryGetReference("/folder1/folder 2/file2.txt", out res)); Assert.IsTrue(file3.TryGetReference("/folder1/folder 2/file2.txt", out res));
Assert.AreEqual(file2, 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));
}
/// <summary>
/// Tests the collection notify behavior
/// </summary>
[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);
}
/// <summary>
/// Tests the property notify behavior
/// </summary>
[TestMethod]
public void ReferencePropertyChangedTest()
{
List<string> propNames = new List<string>();
// 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"));
}
/// <summary>
/// Tests the automatic detection of target kinds
/// </summary>
[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);
} }
} }
} }

View File

@ -57,6 +57,10 @@
</Otherwise> </Otherwise>
</Choose> </Choose>
<ItemGroup> <ItemGroup>
<Compile Include="Business\DocumentManagerTest.cs" />
<Compile Include="Business\ProjectManagerClipboardTest.cs" />
<Compile Include="Business\ProjectManagerTest.cs" />
<Compile Include="Business\ProjectManagerTestBase.cs" />
<Compile Include="Model\ReferenceTest.cs" /> <Compile Include="Model\ReferenceTest.cs" />
<Compile Include="Storage\ProjectStorageTest.cs" /> <Compile Include="Storage\ProjectStorageTest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />

View File

@ -3,6 +3,7 @@ using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using RainmeterStudio.Core.Model; using RainmeterStudio.Core.Model;
using RainmeterStudio.Editor.ProjectEditor; using RainmeterStudio.Editor.ProjectEditor;
using RainmeterStudio.Storage;
using Version = RainmeterStudio.Core.Utils.Version; using Version = RainmeterStudio.Core.Utils.Version;
namespace RainmeterStudio.Tests.Storage namespace RainmeterStudio.Tests.Storage
@ -13,8 +14,6 @@ namespace RainmeterStudio.Tests.Storage
[TestClass] [TestClass]
public class ProjectStorageTest public class ProjectStorageTest
{ {
private ProjectStorage ProjectStorage = new ProjectStorage();
public TestContext TestContext { get; set; } public TestContext TestContext { get; set; }
[TestInitialize] [TestInitialize]

View File

@ -3,9 +3,14 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using System.Xml.Serialization;
using RainmeterStudio.Core.Model; using RainmeterStudio.Core.Model;
using RainmeterStudio.Core.Storage; using RainmeterStudio.Core.Storage;
using RainmeterStudio.Core.Utils;
using RainmeterStudio.Editor.ProjectEditor; using RainmeterStudio.Editor.ProjectEditor;
using RainmeterStudio.Storage;
namespace RainmeterStudio.Business namespace RainmeterStudio.Business
{ {
@ -20,11 +25,6 @@ namespace RainmeterStudio.Business
/// </summary> /// </summary>
public Project ActiveProject { get; protected set; } public Project ActiveProject { get; protected set; }
/// <summary>
/// Gets or sets the project storage
/// </summary>
protected ProjectStorage Storage { get; set; }
#endregion #endregion
#region Events #region Events
@ -42,9 +42,8 @@ namespace RainmeterStudio.Business
/// Initializes the project manager /// Initializes the project manager
/// </summary> /// </summary>
/// <param name="storage">Project storage</param> /// <param name="storage">Project storage</param>
public ProjectManager(ProjectStorage storage) public ProjectManager()
{ {
Storage = storage;
ActiveProject = null; ActiveProject = null;
} }
@ -69,7 +68,10 @@ namespace RainmeterStudio.Business
ActiveProject.Path = path; ActiveProject.Path = path;
// Save to file // Save to file
string directory = Path.GetDirectoryName(path);
if (!String.IsNullOrEmpty(directory))
Directory.CreateDirectory(Path.GetDirectoryName(path)); Directory.CreateDirectory(Path.GetDirectoryName(path));
SaveActiveProject(); SaveActiveProject();
// Raise event // Raise event
@ -88,7 +90,7 @@ namespace RainmeterStudio.Business
Close(); Close();
// Open using storage // Open using storage
ActiveProject = Storage.Read(path); ActiveProject = ProjectStorage.Read(path);
ActiveProject.Path = path; ActiveProject.Path = path;
// Raise event // Raise event
@ -106,7 +108,7 @@ namespace RainmeterStudio.Business
throw new InvalidOperationException("Cannot save a project that is not opened."); throw new InvalidOperationException("Cannot save a project that is not opened.");
// Save // Save
Storage.Write(ActiveProject); ProjectStorage.Write(ActiveProject);
} }
/// <summary> /// <summary>
@ -140,5 +142,238 @@ namespace RainmeterStudio.Business
public IEnumerable<IProjectTemplate> ProjectTemplates { get { return _projectTemplates; } } public IEnumerable<IProjectTemplate> ProjectTemplates { get { return _projectTemplates; } }
#endregion #endregion
#region Project item operations
[Serializable]
protected struct ClipboardData
{
public bool Cut;
public string QualifiedName;
}
/// <summary>
/// Places a project item in the clipboard, and marks it for deletion
/// </summary>
/// <param name="ref">Project item to cut</param>
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);
}
/// <summary>
/// Places a project item in the clipboard
/// </summary>
/// <param name="ref">Project item to copy</param>
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);
}
/// <summary>
/// Pastes a project item from clipboard
/// </summary>
/// <param name="dest">Destination</param>
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);
}
}
}
/// <summary>
/// Moves a project item to another folder
/// </summary>
/// <param name="ref">Project item to move</param>
/// <param name="dest">Destination folder</param>
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);
}
}
/// <summary>
/// Creates a copy of a project item to another folder
/// </summary>
/// <param name="ref">Project item to copy</param>
/// <param name="dest">Destination folder</param>
/// <returns>Reference to the copy</returns>
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;
}
/// <summary>
/// Deletes a project item
/// </summary>
/// <param name="ref"></param>
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();
}
/// <summary>
/// Checks if there is a project item in the clipboard
/// </summary>
/// <returns>True if there is a project item in the clipboard</returns>
public bool HaveProjectItemInClipboard()
{
var dataFormat = DataFormats.GetDataFormat(typeof(ClipboardData).FullName);
return Clipboard.ContainsData(dataFormat.Name);
}
/// <summary>
/// Creates a new folder with given name
/// </summary>
/// <param name="name">Name of folder</param>
/// <param name="parent">Parent folder</param>
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
} }
} }

View File

@ -7,6 +7,7 @@ using System.Xml.Serialization;
using RainmeterStudio.Core; using RainmeterStudio.Core;
using RainmeterStudio.Core.Model; using RainmeterStudio.Core.Model;
using RainmeterStudio.Core.Storage; using RainmeterStudio.Core.Storage;
using RainmeterStudio.Storage;
namespace RainmeterStudio.Editor.ProjectEditor namespace RainmeterStudio.Editor.ProjectEditor
{ {
@ -14,60 +15,8 @@ namespace RainmeterStudio.Editor.ProjectEditor
/// Project storage, loads and saves project files /// Project storage, loads and saves project files
/// </summary> /// </summary>
[PluginExport] [PluginExport]
public class ProjectStorage : IDocumentStorage public class ProjectDocumentStorage : IDocumentStorage
{ {
/// <summary>
/// Loads a project from file
/// </summary>
/// <param name="path">Path to file to load</param>
/// <returns>Loaded project</returns>
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;
}
/// <summary>
/// Saves a project to file
/// </summary>
/// <param name="project">Project to save</param>
/// <param name="path">File to save to</param>
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;
}
/// <summary>
/// Saves a project
/// </summary>
/// <param name="project">Saves a project to the path specified in the 'Path' property</param>
public void Write(Project project)
{
Write(project, project.Path);
}
/// <summary> /// <summary>
/// Reads the project as a ProjectDocument. /// Reads the project as a ProjectDocument.
/// Use Load to get only the Project. /// Use Load to get only the Project.
@ -76,9 +25,9 @@ namespace RainmeterStudio.Editor.ProjectEditor
/// <returns>A project document</returns> /// <returns>A project document</returns>
public IDocument ReadDocument(string path) public IDocument ReadDocument(string path)
{ {
Project project = Read(path); Project project = ProjectStorage.Read(path);
var document = new ProjectDocument(project); 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; return document;
} }
@ -91,7 +40,7 @@ namespace RainmeterStudio.Editor.ProjectEditor
public void WriteDocument(IDocument document, string path) public void WriteDocument(IDocument document, string path)
{ {
var projectDocument = (ProjectDocument)document; var projectDocument = (ProjectDocument)document;
Write(projectDocument.Project, path); ProjectStorage.Write(projectDocument.Project, path);
} }
/// <summary> /// <summary>

View File

@ -23,11 +23,8 @@ namespace RainmeterStudio
SplashScreen splash = new SplashScreen("Resources/splash.png"); SplashScreen splash = new SplashScreen("Resources/splash.png");
splash.Show(true); splash.Show(true);
// Initialize project manager // Initialize project, document manager
ProjectStorage projectStorage = new ProjectStorage(); ProjectManager projectManager = new ProjectManager();
ProjectManager projectManager = new ProjectManager(projectStorage);
// Initialize document manager
DocumentManager documentManager = new DocumentManager(); DocumentManager documentManager = new DocumentManager();
// Initialize plugin manager // Initialize plugin manager

View File

@ -96,14 +96,12 @@
<DesignTime>True</DesignTime> <DesignTime>True</DesignTime>
<DependentUpon>Strings.resx</DependentUpon> <DependentUpon>Strings.resx</DependentUpon>
</Compile> </Compile>
<Compile Include="Editor\ProjectEditor\ProjectStorage.cs" /> <Compile Include="Editor\ProjectEditor\ProjectDocumentStorage.cs" />
<Compile Include="Storage\ProjectStorage.cs" />
<Compile Include="UI\Command.cs" /> <Compile Include="UI\Command.cs" />
<Compile Include="UI\CommandGroup.cs" />
<Compile Include="UI\Controller\IconProvider.cs" /> <Compile Include="UI\Controller\IconProvider.cs" />
<Compile Include="UI\Controller\IContextMenuProvider.cs" />
<Compile Include="UI\Controller\ProjectController.cs" /> <Compile Include="UI\Controller\ProjectController.cs" />
<Compile Include="Business\SettingsProvider.cs" /> <Compile Include="Business\SettingsProvider.cs" />
<Compile Include="UI\Controller\ReferenceContextMenuProvider.cs" />
<Compile Include="UI\Dialogs\CloseUnsavedDialog.xaml.cs"> <Compile Include="UI\Dialogs\CloseUnsavedDialog.xaml.cs">
<DependentUpon>CloseUnsavedDialog.xaml</DependentUpon> <DependentUpon>CloseUnsavedDialog.xaml</DependentUpon>
</Compile> </Compile>
@ -114,6 +112,9 @@
<Compile Include="UI\Dialogs\CreateProjectDialog.xaml.cs"> <Compile Include="UI\Dialogs\CreateProjectDialog.xaml.cs">
<DependentUpon>CreateProjectDialog.xaml</DependentUpon> <DependentUpon>CreateProjectDialog.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="UI\Dialogs\InputDialog.xaml.cs">
<DependentUpon>InputDialog.xaml</DependentUpon>
</Compile>
<Compile Include="UI\Panels\ProjectPanel.xaml.cs"> <Compile Include="UI\Panels\ProjectPanel.xaml.cs">
<DependentUpon>ProjectPanel.xaml</DependentUpon> <DependentUpon>ProjectPanel.xaml</DependentUpon>
</Compile> </Compile>
@ -133,6 +134,10 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Include="UI\Dialogs\InputDialog.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="UI\MainWindow.xaml"> <Page Include="UI\MainWindow.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
@ -260,9 +265,7 @@
<Resource Include="Resources\Icons\16\disk.png" /> <Resource Include="Resources\Icons\16\disk.png" />
<Resource Include="Resources\Icons\16\disk_multiple.png" /> <Resource Include="Resources\Icons\16\disk_multiple.png" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup />
<Folder Include="Storage\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -108,6 +108,15 @@ namespace RainmeterStudio.Resources {
/// <summary> /// <summary>
/// Looks up a localized string similar to _File.... /// Looks up a localized string similar to _File....
/// </summary> /// </summary>
public static string Command_DocumentCreate_AltDisplayText {
get {
return ResourceManager.GetString("Command_DocumentCreate_AltDisplayText", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to _New item.
/// </summary>
public static string Command_DocumentCreate_DisplayText { public static string Command_DocumentCreate_DisplayText {
get { get {
return ResourceManager.GetString("Command_DocumentCreate_DisplayText", resourceCulture); return ResourceManager.GetString("Command_DocumentCreate_DisplayText", resourceCulture);
@ -126,6 +135,15 @@ namespace RainmeterStudio.Resources {
/// <summary> /// <summary>
/// Looks up a localized string similar to _File.... /// Looks up a localized string similar to _File....
/// </summary> /// </summary>
public static string Command_DocumentOpen_AltDisplayText {
get {
return ResourceManager.GetString("Command_DocumentOpen_AltDisplayText", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to _Open.
/// </summary>
public static string Command_DocumentOpen_DisplayText { public static string Command_DocumentOpen_DisplayText {
get { get {
return ResourceManager.GetString("Command_DocumentOpen_DisplayText", resourceCulture); return ResourceManager.GetString("Command_DocumentOpen_DisplayText", resourceCulture);
@ -234,6 +252,15 @@ namespace RainmeterStudio.Resources {
/// <summary> /// <summary>
/// Looks up a localized string similar to _Project.... /// Looks up a localized string similar to _Project....
/// </summary> /// </summary>
public static string Command_ProjectCreate_AltDisplayText {
get {
return ResourceManager.GetString("Command_ProjectCreate_AltDisplayText", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to New _Project.
/// </summary>
public static string Command_ProjectCreate_DisplayText { public static string Command_ProjectCreate_DisplayText {
get { get {
return ResourceManager.GetString("Command_ProjectCreate_DisplayText", resourceCulture); return ResourceManager.GetString("Command_ProjectCreate_DisplayText", resourceCulture);
@ -249,9 +276,144 @@ namespace RainmeterStudio.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to _Copy.
/// </summary>
public static string Command_ProjectItemCopy_DisplayText {
get {
return ResourceManager.GetString("Command_ProjectItemCopy_DisplayText", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Copy the selected item to clipboard.
/// </summary>
public static string Command_ProjectItemCopy_ToolTip {
get {
return ResourceManager.GetString("Command_ProjectItemCopy_ToolTip", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cu_t.
/// </summary>
public static string Command_ProjectItemCut_DisplayText {
get {
return ResourceManager.GetString("Command_ProjectItemCut_DisplayText", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cut the selected item to clipboard.
/// </summary>
public static string Command_ProjectItemCut_ToolTip {
get {
return ResourceManager.GetString("Command_ProjectItemCut_ToolTip", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to _Delete.
/// </summary>
public static string Command_ProjectItemDelete_DisplayText {
get {
return ResourceManager.GetString("Command_ProjectItemDelete_DisplayText", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Delete selected item.
/// </summary>
public static string Command_ProjectItemDelete_ToolTip {
get {
return ResourceManager.GetString("Command_ProjectItemDelete_ToolTip", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Open Containing _Folder.
/// </summary>
public static string Command_ProjectItemOpenContainingFolder_DisplayText {
get {
return ResourceManager.GetString("Command_ProjectItemOpenContainingFolder_DisplayText", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Open containing folder of selected item in Explorer.
/// </summary>
public static string Command_ProjectItemOpenContainingFolder_ToolTip {
get {
return ResourceManager.GetString("Command_ProjectItemOpenContainingFolder_ToolTip", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Open in _Explorer.
/// </summary>
public static string Command_ProjectItemOpenInExplorer_DisplayText {
get {
return ResourceManager.GetString("Command_ProjectItemOpenInExplorer_DisplayText", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Open selected folder in Explorer.
/// </summary>
public static string Command_ProjectItemOpenInExplorer_ToolTip {
get {
return ResourceManager.GetString("Command_ProjectItemOpenInExplorer_ToolTip", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to _Paste.
/// </summary>
public static string Command_ProjectItemPaste_DisplayText {
get {
return ResourceManager.GetString("Command_ProjectItemPaste_DisplayText", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Paste the clipboard contents in selected folder.
/// </summary>
public static string Command_ProjectItemPaste_ToolTip {
get {
return ResourceManager.GetString("Command_ProjectItemPaste_ToolTip", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to _Rename.
/// </summary>
public static string Command_ProjectItemRename_DisplayText {
get {
return ResourceManager.GetString("Command_ProjectItemRename_DisplayText", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Rename selected item.
/// </summary>
public static string Command_ProjectItemRename_ToolTip {
get {
return ResourceManager.GetString("Command_ProjectItemRename_ToolTip", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to _Project.... /// Looks up a localized string similar to _Project....
/// </summary> /// </summary>
public static string Command_ProjectOpen_AltDisplayText {
get {
return ResourceManager.GetString("Command_ProjectOpen_AltDisplayText", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to O_pen Project.
/// </summary>
public static string Command_ProjectOpen_DisplayText { public static string Command_ProjectOpen_DisplayText {
get { get {
return ResourceManager.GetString("Command_ProjectOpen_DisplayText", resourceCulture); return ResourceManager.GetString("Command_ProjectOpen_DisplayText", resourceCulture);
@ -447,6 +609,24 @@ namespace RainmeterStudio.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Do you also want to delete the selected project items from disk?.
/// </summary>
public static string DeleteReferenceDialog_Caption {
get {
return ResourceManager.GetString("DeleteReferenceDialog_Caption", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Delete from disk?.
/// </summary>
public static string DeleteReferenceDialog_Prompt {
get {
return ResourceManager.GetString("DeleteReferenceDialog_Prompt", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Browse. /// Looks up a localized string similar to Browse.
/// </summary> /// </summary>
@ -599,5 +779,32 @@ namespace RainmeterStudio.Resources {
return ResourceManager.GetString("ProjectTemplate_EmptyProject_DisplayText", resourceCulture); return ResourceManager.GetString("ProjectTemplate_EmptyProject_DisplayText", resourceCulture);
} }
} }
/// <summary>
/// Looks up a localized string similar to Rename project item.
/// </summary>
public static string RenameReferenceDialog_Caption {
get {
return ResourceManager.GetString("RenameReferenceDialog_Caption", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Rename.
/// </summary>
public static string RenameReferenceDialog_OKCaption {
get {
return ResourceManager.GetString("RenameReferenceDialog_OKCaption", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Enter the new name:.
/// </summary>
public static string RenameReferenceDialog_Prompt {
get {
return ResourceManager.GetString("RenameReferenceDialog_Prompt", resourceCulture);
}
}
} }
} }

View File

@ -154,7 +154,7 @@
<value>Close active document</value> <value>Close active document</value>
</data> </data>
<data name="Command_DocumentCreate_DisplayText" xml:space="preserve"> <data name="Command_DocumentCreate_DisplayText" xml:space="preserve">
<value>_File...</value> <value>_New item</value>
</data> </data>
<data name="Command_DocumentCreate_ToolTip" xml:space="preserve"> <data name="Command_DocumentCreate_ToolTip" xml:space="preserve">
<value>Create a new file</value> <value>Create a new file</value>
@ -168,7 +168,7 @@
<data name="MainWindow_File_Open" xml:space="preserve"> <data name="MainWindow_File_Open" xml:space="preserve">
<value>_Open</value> <value>_Open</value>
</data> </data>
<data name="Command_ProjectCreate_DisplayText" xml:space="preserve"> <data name="Command_ProjectCreate_AltDisplayText" xml:space="preserve">
<value>_Project...</value> <value>_Project...</value>
</data> </data>
<data name="Command_ProjectCreate_ToolTip" xml:space="preserve"> <data name="Command_ProjectCreate_ToolTip" xml:space="preserve">
@ -192,7 +192,7 @@
<data name="CreateProjectDialog_Title" xml:space="preserve"> <data name="CreateProjectDialog_Title" xml:space="preserve">
<value>Create project</value> <value>Create project</value>
</data> </data>
<data name="Command_ProjectOpen_DisplayText" xml:space="preserve"> <data name="Command_ProjectOpen_AltDisplayText" xml:space="preserve">
<value>_Project...</value> <value>_Project...</value>
</data> </data>
<data name="Command_ProjectOpen_ToolTip" xml:space="preserve"> <data name="Command_ProjectOpen_ToolTip" xml:space="preserve">
@ -268,7 +268,7 @@
<value>Save document</value> <value>Save document</value>
</data> </data>
<data name="Command_DocumentOpen_DisplayText" xml:space="preserve"> <data name="Command_DocumentOpen_DisplayText" xml:space="preserve">
<value>_File...</value> <value>_Open</value>
</data> </data>
<data name="Command_DocumentOpen_ToolTip" xml:space="preserve"> <data name="Command_DocumentOpen_ToolTip" xml:space="preserve">
<value>Open a file</value> <value>Open a file</value>
@ -297,4 +297,73 @@
<data name="Command_DocumentSave_ToolTip" xml:space="preserve"> <data name="Command_DocumentSave_ToolTip" xml:space="preserve">
<value>Save the current document</value> <value>Save the current document</value>
</data> </data>
<data name="DeleteReferenceDialog_Caption" xml:space="preserve">
<value>Do you also want to delete the selected project items from disk?</value>
</data>
<data name="DeleteReferenceDialog_Prompt" xml:space="preserve">
<value>Delete from disk?</value>
</data>
<data name="RenameReferenceDialog_Caption" xml:space="preserve">
<value>Rename project item</value>
</data>
<data name="RenameReferenceDialog_OKCaption" xml:space="preserve">
<value>Rename</value>
</data>
<data name="RenameReferenceDialog_Prompt" xml:space="preserve">
<value>Enter the new name:</value>
</data>
<data name="Command_DocumentCreate_AltDisplayText" xml:space="preserve">
<value>_File...</value>
</data>
<data name="Command_DocumentOpen_AltDisplayText" xml:space="preserve">
<value>_File...</value>
</data>
<data name="Command_ProjectItemCopy_DisplayText" xml:space="preserve">
<value>_Copy</value>
</data>
<data name="Command_ProjectItemCopy_ToolTip" xml:space="preserve">
<value>Copy the selected item to clipboard</value>
</data>
<data name="Command_ProjectItemCut_DisplayText" xml:space="preserve">
<value>Cu_t</value>
</data>
<data name="Command_ProjectItemCut_ToolTip" xml:space="preserve">
<value>Cut the selected item to clipboard</value>
</data>
<data name="Command_ProjectItemDelete_DisplayText" xml:space="preserve">
<value>_Delete</value>
</data>
<data name="Command_ProjectItemDelete_ToolTip" xml:space="preserve">
<value>Delete selected item</value>
</data>
<data name="Command_ProjectItemOpenContainingFolder_DisplayText" xml:space="preserve">
<value>Open Containing _Folder</value>
</data>
<data name="Command_ProjectItemOpenContainingFolder_ToolTip" xml:space="preserve">
<value>Open containing folder of selected item in Explorer</value>
</data>
<data name="Command_ProjectItemOpenInExplorer_DisplayText" xml:space="preserve">
<value>Open in _Explorer</value>
</data>
<data name="Command_ProjectItemOpenInExplorer_ToolTip" xml:space="preserve">
<value>Open selected folder in Explorer</value>
</data>
<data name="Command_ProjectItemPaste_DisplayText" xml:space="preserve">
<value>_Paste</value>
</data>
<data name="Command_ProjectItemPaste_ToolTip" xml:space="preserve">
<value>Paste the clipboard contents in selected folder</value>
</data>
<data name="Command_ProjectItemRename_DisplayText" xml:space="preserve">
<value>_Rename</value>
</data>
<data name="Command_ProjectItemRename_ToolTip" xml:space="preserve">
<value>Rename selected item</value>
</data>
<data name="Command_ProjectCreate_DisplayText" xml:space="preserve">
<value>New _Project</value>
</data>
<data name="Command_ProjectOpen_DisplayText" xml:space="preserve">
<value>O_pen Project</value>
</data>
</root> </root>

View File

@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
using RainmeterStudio.Core.Model;
namespace RainmeterStudio.Storage
{
/// <summary>
/// Reads or writes projects
/// </summary>
public static class ProjectStorage
{
/// <summary>
/// Loads a project from file
/// </summary>
/// <param name="path">Path to file to load</param>
/// <returns>Loaded project</returns>
public static 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;
}
/// <summary>
/// Saves a project
/// </summary>
/// <param name="project">Saves a project to the path specified in the 'Path' property</param>
public static void Write(Project project)
{
Write(project, project.Path);
}
/// <summary>
/// Saves a project to file
/// </summary>
/// <param name="project">Project to save</param>
/// <param name="path">File to save to</param>
public static 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;
}
}
}

View File

@ -14,6 +14,12 @@
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />
</Style> </Style>
<Style x:Key="CommandContextMenuItemStyle" TargetType="MenuItem">
<Setter Property="Command" Value="{Binding}" />
<Setter Property="Header" Value="{Binding DisplayText}" />
<Setter Property="ToolTip" Value="{Binding ToolTip}" />
</Style>
<Style x:Key="CommandMenuItemStyle" TargetType="MenuItem"> <Style x:Key="CommandMenuItemStyle" TargetType="MenuItem">
<Setter Property="Command" Value="{Binding}" /> <Setter Property="Command" Value="{Binding}" />
<Setter Property="Header" Value="{Binding DisplayText}" /> <Setter Property="Header" Value="{Binding DisplayText}" />

View File

@ -114,7 +114,17 @@ namespace RainmeterStudio.UI.Controller
ProjectManager = projectManager; ProjectManager = projectManager;
DocumentCreateCommand = new Command("DocumentCreate", Create, () => ProjectManager.ActiveProject != null); DocumentCreateCommand = new Command("DocumentCreate", Create, () => ProjectManager.ActiveProject != null);
DocumentOpenCommand = new Command("DocumentOpen", Open); DocumentOpenCommand = new Command("DocumentOpen", arg =>
{
if (arg is Reference)
{
Open((Reference)arg);
}
else
{
Open();
}
});
DocumentSaveCommand = new Command("DocumentSave", () => Save(), HasActiveDocumentEditor); DocumentSaveCommand = new Command("DocumentSave", () => Save(), HasActiveDocumentEditor);
DocumentSaveAsCommand = new Command("DocumentSaveAs", () => SaveAs(), HasActiveDocumentEditor); DocumentSaveAsCommand = new Command("DocumentSaveAs", () => SaveAs(), HasActiveDocumentEditor);
DocumentSaveACopyCommand = new Command("DocumentSaveACopy", () => SaveACopy(), HasActiveDocumentEditor); DocumentSaveACopyCommand = new Command("DocumentSaveACopy", () => SaveACopy(), HasActiveDocumentEditor);
@ -168,7 +178,7 @@ namespace RainmeterStudio.UI.Controller
if (!Directory.Exists(folder)) if (!Directory.Exists(folder))
folder = Path.GetDirectoryName(folder); folder = Path.GetDirectoryName(folder);
var reference = new Reference(name, Path.Combine(folder, name), Reference.ReferenceTargetKind.File); var reference = new Reference(name, Path.Combine(folder, name), ReferenceTargetKind.File);
editor.AttachedDocument.Reference = reference; editor.AttachedDocument.Reference = reference;
// Save document // Save document
@ -198,6 +208,15 @@ namespace RainmeterStudio.UI.Controller
} }
} }
/// <summary>
/// Opens the document pointed to by a reference
/// </summary>
/// <param name="reference"></param>
public void Open(Reference reference)
{
DocumentManager.Open(reference.StoragePath);
}
/// <summary> /// <summary>
/// Saves the active document /// Saves the active document
/// </summary> /// </summary>

View File

@ -33,7 +33,7 @@ namespace RainmeterStudio.UI.Controller
string key = "ProjectItem"; string key = "ProjectItem";
// Is a file? // Is a file?
if (item.TargetKind == Reference.ReferenceTargetKind.File || item.TargetKind == Reference.ReferenceTargetKind.Project) if (item.TargetKind == ReferenceTargetKind.File || item.TargetKind == ReferenceTargetKind.Project)
{ {
var extension = Path.GetExtension(item.StoragePath); var extension = Path.GetExtension(item.StoragePath);
@ -45,7 +45,7 @@ namespace RainmeterStudio.UI.Controller
} }
// Not a file, try to figure out if a directory // Not a file, try to figure out if a directory
else if (item.TargetKind == Reference.ReferenceTargetKind.Directory) else if (item.TargetKind == ReferenceTargetKind.Directory)
{ {
key += "Directory"; key += "Directory";
} }

View File

@ -9,6 +9,7 @@ using RainmeterStudio.Core.Model;
using RainmeterStudio.UI.Dialogs; using RainmeterStudio.UI.Dialogs;
using RainmeterStudio.UI.ViewModel; using RainmeterStudio.UI.ViewModel;
using RainmeterStudio.Properties; using RainmeterStudio.Properties;
using RainmeterStudio.Core.Utils;
namespace RainmeterStudio.UI.Controller namespace RainmeterStudio.UI.Controller
{ {
@ -97,6 +98,42 @@ namespace RainmeterStudio.UI.Controller
/// </summary> /// </summary>
public Command ProjectCloseCommand { get; private set; } public Command ProjectCloseCommand { get; private set; }
/// <summary>
/// Cut command
/// </summary>
public Command ProjectItemCutCommand { get; private set; }
/// <summary>
/// Copy command
/// </summary>
public Command ProjectItemCopyCommand { get; private set; }
/// <summary>
/// Paste command
/// </summary>
public Command ProjectItemPasteCommand { get; private set; }
/// <summary>
/// Rename command
/// </summary>
public Command ProjectItemRenameCommand { get; private set; }
/// <summary>
/// Delete command
/// </summary>
public Command ProjectItemDeleteCommand { get; private set; }
/// <summary>
/// Open folder command
/// </summary>
public Command ProjectItemOpenInExplorerCommand { get; private set; }
/// <summary>
/// Open folder command
/// </summary>
public Command ProjectItemOpenContainingFolderCommand { get; private set; }
#endregion #endregion
/// <summary> /// <summary>
@ -111,9 +148,19 @@ namespace RainmeterStudio.UI.Controller
ProjectCreateCommand = new Command("ProjectCreate", CreateProject); ProjectCreateCommand = new Command("ProjectCreate", CreateProject);
ProjectOpenCommand = new Command("ProjectOpen", OpenProject); ProjectOpenCommand = new Command("ProjectOpen", OpenProject);
ProjectCloseCommand = new Command("ProjectClose", CloseProject, () => ActiveProject != null); ProjectCloseCommand = new Command("ProjectClose", CloseProject, () => ActiveProject != null);
ProjectItemCutCommand = new Command("ProjectItemCut", r => ProjectItemCutClipboard((Reference)r));
ProjectItemCopyCommand = new Command("ProjectItemCopy", r => ProjectItemCopyClipboard((Reference)r));
ProjectItemPasteCommand = new Command("ProjectItemPaste", r => ProjectItemPasteClipboard((Reference)r), r => Manager.HaveProjectItemInClipboard());
ProjectItemRenameCommand = new Command("ProjectItemRename", r => ProjectItemRename((Reference)r));
ProjectItemDeleteCommand = new Command("ProjectItemDelete", r => ProjectItemDelete((Reference)r));
ProjectItemOpenInExplorerCommand = new Command("ProjectItemOpenInExplorer", r => ProjectItemOpenInExplorer((Reference)r));
ProjectItemOpenContainingFolderCommand = new Command("ProjectItemOpenContainingFolder", r => ProjectItemOpenInExplorer((Reference)r));
ActiveProjectChanged += new EventHandler((sender, e) => ProjectCloseCommand.NotifyCanExecuteChanged()); ActiveProjectChanged += new EventHandler((sender, e) => ProjectCloseCommand.NotifyCanExecuteChanged());
} }
#region Project operations
/// <summary> /// <summary>
/// Displays the 'create project' dialog and creates a new project /// Displays the 'create project' dialog and creates a new project
/// </summary> /// </summary>
@ -167,5 +214,144 @@ namespace RainmeterStudio.UI.Controller
{ {
Manager.Close(); Manager.Close();
} }
#endregion
#region Project item operations
protected struct ClipboardData
{
public bool Cut;
public Reference Ref;
}
/// <summary>
/// Places a project item in the clipboard, and marks it for deletion
/// </summary>
/// <param name="ref">Project item to cut</param>
public void ProjectItemCutClipboard(Reference @ref)
{
try
{
Manager.ProjectItemCutClipboard(@ref);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error");
}
}
/// <summary>
/// Places a project item in the clipboard
/// </summary>
/// <param name="ref">Project item to copy</param>
public void ProjectItemCopyClipboard(Reference @ref)
{
try
{
Manager.ProjectItemCopyClipboard(@ref);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error");
}
}
/// <summary>
/// Pastes a project item from clipboard
/// </summary>
/// <param name="ref">Destination</param>
public void ProjectItemPasteClipboard(Reference @ref)
{
try
{
Manager.ProjectItemPasteClipboard(@ref);
Manager.SaveActiveProject();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error");
}
}
/// <summary>
/// Renames a project item
/// </summary>
/// <param name="ref">Reference to project item</param>
public void ProjectItemRename(Reference @ref)
{
string initialValue = Path.GetFileName(@ref.StoragePath.TrimEnd('\\'));
// Show an input dialog
var newName = InputDialog.Show(Resources.Strings.RenameReferenceDialog_Prompt,
Resources.Strings.RenameReferenceDialog_Caption,
initialValue,
PathHelper.IsFileNameValid,
Resources.Strings.RenameReferenceDialog_OKCaption,
Resources.Strings.Dialog_Cancel);
if (newName != null)
{
try
{
Manager.ProjectItemRename(@ref, newName);
Manager.SaveActiveProject();
}
catch(Exception ex)
{
MessageBox.Show(ex.Message, "Error");
}
}
}
/// <summary>
/// Deletes a project item
/// </summary>
/// <param name="ref">Reference to project item</param>
public void ProjectItemDelete(Reference @ref)
{
var res = MessageBox.Show(Resources.Strings.DeleteReferenceDialog_Prompt,
Resources.Strings.DeleteReferenceDialog_Caption,
MessageBoxButton.YesNoCancel,
MessageBoxImage.Question);
try
{
switch(res)
{
case MessageBoxResult.Yes:
Manager.ProjectItemDelete(@ref, true);
Manager.SaveActiveProject();
break;
case MessageBoxResult.No:
Manager.ProjectItemDelete(@ref, false);
Manager.SaveActiveProject();
break;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error");
}
}
/// <summary>
/// Opens the containing folder if reference is a file, or folder if reference is a folder in windows explorer
/// </summary>
/// <param name="ref">Reference</param>
public void ProjectItemOpenInExplorer(Reference @ref)
{
if (@ref.TargetKind == ReferenceTargetKind.Directory)
{
System.Diagnostics.Process.Start(@ref.StoragePath);
}
else
{
System.Diagnostics.Process.Start(Path.GetDirectoryName(@ref.StoragePath));
}
}
#endregion
} }
} }

View File

@ -0,0 +1,29 @@
<Window x:Class="RainmeterStudio.UI.Dialogs.InputDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:r="clr-namespace:RainmeterStudio.Resources"
Height="100" Width="360"
WindowStartupLocation="CenterOwner"
WindowStyle="ToolWindow" ShowInTaskbar="False"
Background="WhiteSmoke" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Name="textPrompt" Grid.Row="0"
Text="[placeholder]" Margin="4" Grid.ColumnSpan="2"/>
<TextBox Name="textInput" Grid.Row="1" Margin="3"
TextChanged="textInput_TextChanged" Grid.ColumnSpan="2"/>
<StackPanel Grid.Row="2" Orientation="Horizontal"
HorizontalAlignment="Right" Grid.Column="1">
<Button Name="buttonOK" Content="{x:Static r:Strings.Dialog_OK}" IsDefault="True" Margin="1px" Click="buttonOK_Click" />
<Button Name="buttonCancel" Content="{x:Static r:Strings.Dialog_Cancel}" IsCancel="True" Margin="1px" Click="buttonCancel_Click" />
</StackPanel>
</Grid>
</Window>

View File

@ -0,0 +1,380 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using RainmeterStudio.Resources;
namespace RainmeterStudio.UI.Dialogs
{
/// <summary>
/// Interaction logic for InputDialog.xaml
/// </summary>
public partial class InputDialog : Window
{
private Func<string, bool> _validateFunction;
/// <summary>
/// A validate function that always returns true
/// </summary>
public static readonly Func<string, bool> AlwaysValid = (str => true);
#region Properties
/// <summary>
/// Gets or sets the prompt text
/// </summary>
public string Prompt
{
get
{
return textPrompt.Text;
}
set
{
textPrompt.Text = value;
}
}
/// <summary>
/// Gets or sets the text on the 'ok' button
/// </summary>
public string OKCaption
{
get
{
return (string)buttonOK.Content;
}
set
{
buttonOK.Content = value;
}
}
/// <summary>
/// Gets or sets the text on the 'cancel' button
/// </summary>
public string CancelCaption
{
get
{
return (string)buttonCancel.Content;
}
set
{
buttonCancel.Content = value;
}
}
/// <summary>
/// Gets or sets the text inputted by the user
/// </summary>
public string InputText
{
get
{
return textInput.Text;
}
set
{
textInput.Text = value;
Validate();
}
}
/// <summary>
/// Gets or sets a function used to validate the input
/// </summary>
public Func<string, bool> ValidateFunction
{
get
{
return _validateFunction;
}
set
{
_validateFunction = value;
Validate();
}
}
#endregion
/// <summary>
/// Initializes the input dialog
/// </summary>
/// <param name="prompt">Message displayed to user</param>
public InputDialog(string prompt)
:this(prompt, String.Empty, String.Empty, AlwaysValid)
{
}
/// <summary>
/// Initializes the input dialog
/// </summary>
/// <param name="prompt">Message displayed to user</param>
/// <param name="caption">Title of dialog</param>
public InputDialog(string prompt, string caption)
: this(prompt, caption, String.Empty, AlwaysValid)
{
}
/// <summary>
/// Initializes the input dialog
/// </summary>
/// <param name="prompt">Message displayed to user</param>
/// <param name="caption">Title of dialog</param>
/// <param name="okCaption">Caption of the 'OK' button</param>
/// <param name="cancelCaption">Caption of the 'Cancel' button</param>
public InputDialog(string prompt, string caption, string okCaption, string cancelCaption)
: this(prompt, caption, String.Empty, AlwaysValid, okCaption, cancelCaption)
{
}
/// <summary>
/// Initializes the input dialog
/// </summary>
/// <param name="prompt">Message displayed to user</param>
/// <param name="caption">Title of dialog</param>
/// <param name="validateFunction">Callback function which validates the inputted text</param>
public InputDialog(string prompt, string caption, Func<string, bool> validateFunction)
: this(prompt, caption, String.Empty, validateFunction)
{
}
/// <summary>
/// Initializes the input dialog
/// </summary>
/// <param name="prompt">Message displayed to user</param>
/// <param name="caption">Title of dialog</param>
/// <param name="validateFunction">Callback function which validates the inputted text</param>
/// <param name="okCaption">Caption of the 'OK' button</param>
/// <param name="cancelCaption">Caption of the 'Cancel' button</param>
public InputDialog(string prompt, string caption, Func<string, bool> validateFunction, string okCaption, string cancelCaption)
: this(prompt, caption, String.Empty, validateFunction, okCaption, cancelCaption)
{
}
/// <summary>
/// Initializes the input dialog
/// </summary>
/// <param name="prompt">Message displayed to user</param>
/// <param name="caption">Title of dialog</param>
/// <param name="initialValue">Initial value of the input dialog</param>
public InputDialog(string prompt, string caption, string initialValue)
: this(prompt, caption, initialValue, AlwaysValid)
{
}
/// <summary>
/// Initializes the input dialog
/// </summary>
/// <param name="prompt">Message displayed to user</param>
/// <param name="caption">Title of dialog</param>
/// <param name="initialValue">Initial value of the input dialog</param>
/// <param name="okCaption">Caption of the 'OK' button</param>
/// <param name="cancelCaption">Caption of the 'Cancel' button</param>
public InputDialog(string prompt, string caption, string initialValue, string okCaption, string cancelCaption)
: this(prompt, caption, initialValue, AlwaysValid, okCaption, cancelCaption)
{
}
/// <summary>
/// Initializes the input dialog
/// </summary>
/// <param name="prompt">Message displayed to user</param>
/// <param name="caption">Title of dialog</param>
/// <param name="initialValue">Initial value of the input dialog</param>
/// <param name="validateFunction">Callback function which validates the inputted text</param>
public InputDialog(string prompt, string caption, string initialValue, Func<string, bool> validateFunction)
: this(prompt, caption, initialValue, validateFunction, Strings.Dialog_OK, Strings.Dialog_Cancel)
{
}
/// <summary>
/// Initializes the input dialog
/// </summary>
/// <param name="prompt">Message displayed to user</param>
/// <param name="caption">Title of dialog</param>
/// <param name="initialValue">Initial value of the input dialog</param>
/// <param name="validateFunction">Callback function which validates the inputted text</param>
/// <param name="okCaption">Caption of the 'OK' button</param>
/// <param name="cancelCaption">Caption of the 'Cancel' button</param>
public InputDialog(string prompt, string caption, string initialValue, Func<string, bool> validateFunction, string okCaption, string cancelCaption)
{
InitializeComponent();
Prompt = prompt;
Title = caption;
InputText = initialValue;
ValidateFunction = validateFunction;
OKCaption = okCaption;
CancelCaption = cancelCaption;
}
private void buttonOK_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
Close();
}
private void buttonCancel_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
Close();
}
private void textInput_TextChanged(object sender, TextChangedEventArgs e)
{
Validate();
}
private void Validate()
{
bool res = true;
if (ValidateFunction != null)
res = ValidateFunction(textInput.Text);
buttonOK.IsEnabled = res;
}
#region Static show
/// <summary>
/// Shows a dialog prompting the user for input
/// </summary>
/// <param name="prompt">Message displayed to user</param>
/// <returns>Input text, or null if canceled</returns>
public static string Show(string prompt)
{
return ShowCommon(new InputDialog(prompt));
}
/// <summary>
/// Shows a dialog prompting the user for input
/// </summary>
/// <param name="prompt">Message displayed to user</param>
/// <param name="caption">Title of dialog</param>
/// <returns>Input text, or null if canceled</returns>
public static string Show(string prompt, string caption)
{
return ShowCommon(new InputDialog(prompt, caption));
}
/// <summary>
/// Shows a dialog prompting the user for input
/// </summary>
/// <param name="prompt">Message displayed to user</param>
/// <param name="caption">Title of dialog</param>
/// <param name="okCaption">Caption of the 'OK' button</param>
/// <param name="cancelCaption">Caption of the 'Cancel' button</param>
/// <returns>Input text, or null if canceled</returns>
public static string Show(string prompt, string caption, string okCaption, string cancelCaption)
{
return ShowCommon(new InputDialog(prompt, caption, okCaption, cancelCaption));
}
/// <summary>
/// Shows a dialog prompting the user for input
/// </summary>
/// <param name="prompt">Message displayed to user</param>
/// <param name="caption">Title of dialog</param>
/// <param name="validateFunction">Callback function which validates the inputted text</param>
/// <returns>Input text, or null if canceled</returns>
public static string Show(string prompt, string caption, Func<string, bool> validateFunction)
{
return ShowCommon(new InputDialog(prompt, caption, validateFunction));
}
/// <summary>
/// Shows a dialog prompting the user for input
/// </summary>
/// <param name="prompt">Message displayed to user</param>
/// <param name="caption">Title of dialog</param>
/// <param name="validateFunction">Callback function which validates the inputted text</param>
/// <param name="okCaption">Caption of the 'OK' button</param>
/// <param name="cancelCaption">Caption of the 'Cancel' button</param>
/// <returns>Input text, or null if canceled</returns>
public static string Show(string prompt, string caption, Func<string, bool> validateFunction, string okCaption, string cancelCaption)
{
return ShowCommon(new InputDialog(prompt, caption, validateFunction, okCaption, cancelCaption));
}
/// <summary>
/// Shows a dialog prompting the user for input
/// </summary>
/// <param name="prompt">Message displayed to user</param>
/// <param name="caption">Title of dialog</param>
/// <param name="initialValue">Initial value of the input dialog</param>
/// <returns>Input text, or null if canceled</returns>
public static string Show(string prompt, string caption, string initialValue)
{
return ShowCommon(new InputDialog(prompt, caption, initialValue));
}
/// <summary>
/// Shows a dialog prompting the user for input
/// </summary>
/// <param name="prompt">Message displayed to user</param>
/// <param name="caption">Title of dialog</param>
/// <param name="initialValue">Initial value of the input dialog</param>
/// <param name="okCaption">Caption of the 'OK' button</param>
/// <param name="cancelCaption">Caption of the 'Cancel' button</param>
/// <returns>Input text, or null if canceled</returns>
public static string Show(string prompt, string caption, string initialValue, string okCaption, string cancelCaption)
{
return ShowCommon(new InputDialog(prompt, caption, initialValue, okCaption, cancelCaption));
}
/// <summary>
/// Shows a dialog prompting the user for input
/// </summary>
/// <param name="prompt">Message displayed to user</param>
/// <param name="caption">Title of dialog</param>
/// <param name="initialValue">Initial value of the input dialog</param>
/// <param name="validateFunction">Callback function which validates the inputted text</param>
/// <returns>Input text, or null if canceled</returns>
public static string Show(string prompt, string caption, string initialValue, Func<string, bool> validateFunction)
{
return ShowCommon(new InputDialog(prompt, caption, initialValue, validateFunction));
}
/// <summary>
/// Shows a dialog prompting the user for input
/// </summary>
/// <param name="prompt">Message displayed to user</param>
/// <param name="caption">Title of dialog</param>
/// <param name="initialValue">Initial value of the input dialog</param>
/// <param name="validateFunction">Callback function which validates the inputted text</param>
/// <param name="okCaption">Caption of the 'OK' button</param>
/// <param name="cancelCaption">Caption of the 'Cancel' button</param>
/// <returns>Input text, or null if canceled</returns>
public static string Show(string prompt, string caption, string initialValue, Func<string, bool> validateFunction, string okCaption, string cancelCaption)
{
return ShowCommon(new InputDialog(prompt, caption, initialValue, validateFunction, okCaption, cancelCaption));
}
private static string ShowCommon(InputDialog dialog)
{
bool? res = dialog.ShowDialog();
if (res.HasValue && res.Value)
{
return dialog.InputText;
}
return null;
}
#endregion
}
}

View File

@ -34,13 +34,15 @@
<MenuItem Header="{x:Static r:Strings.MainWindow_File}"> <MenuItem Header="{x:Static r:Strings.MainWindow_File}">
<MenuItem Header="{x:Static r:Strings.MainWindow_File_New}"> <MenuItem Header="{x:Static r:Strings.MainWindow_File_New}">
<MenuItem DataContext="{Binding DocumentController.DocumentCreateCommand}" <MenuItem DataContext="{Binding DocumentController.DocumentCreateCommand}"
Style="{StaticResource CommandMenuItemStyle}" > Style="{StaticResource CommandMenuItemStyle}"
Header="{x:Static r:Strings.Command_DocumentCreate_AltDisplayText}">
<MenuItem.Icon> <MenuItem.Icon>
<Image Source="{Binding Icon}" /> <Image Source="{Binding Icon}" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem DataContext="{Binding ProjectController.ProjectCreateCommand}" <MenuItem DataContext="{Binding ProjectController.ProjectCreateCommand}"
Style="{StaticResource CommandMenuItemStyle}"> Style="{StaticResource CommandMenuItemStyle}"
Header="{x:Static r:Strings.Command_ProjectCreate_AltDisplayText}">
<MenuItem.Icon> <MenuItem.Icon>
<Image Source="{Binding Icon}" /> <Image Source="{Binding Icon}" />
</MenuItem.Icon> </MenuItem.Icon>
@ -49,13 +51,15 @@
<Separator /> <Separator />
<MenuItem Header="{x:Static r:Strings.MainWindow_File_Open}"> <MenuItem Header="{x:Static r:Strings.MainWindow_File_Open}">
<MenuItem DataContext="{Binding DocumentController.DocumentOpenCommand}" <MenuItem DataContext="{Binding DocumentController.DocumentOpenCommand}"
Style="{StaticResource CommandMenuItemStyle}"> Style="{StaticResource CommandMenuItemStyle}"
Header="{x:Static r:Strings.Command_DocumentOpen_AltDisplayText}">
<MenuItem.Icon> <MenuItem.Icon>
<Image Source="{Binding Icon}" /> <Image Source="{Binding Icon}" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem DataContext="{Binding ProjectController.ProjectOpenCommand}" <MenuItem DataContext="{Binding ProjectController.ProjectOpenCommand}"
Style="{StaticResource CommandMenuItemStyle}"> Style="{StaticResource CommandMenuItemStyle}"
Header="{x:Static r:Strings.Command_ProjectOpen_AltDisplayText}">
<MenuItem.Icon> <MenuItem.Icon>
<Image Source="{Binding Icon}" /> <Image Source="{Binding Icon}" />
</MenuItem.Icon> </MenuItem.Icon>

View File

@ -55,7 +55,8 @@ namespace RainmeterStudio.UI
DocumentController.DocumentOpened += documentController_DocumentOpened; DocumentController.DocumentOpened += documentController_DocumentOpened;
// Initialize panels // Initialize panels
projectPanel.Controller = ProjectController; projectPanel.ProjectController = ProjectController;
projectPanel.DocumentController = DocumentController;
} }
void documentController_DocumentOpened(object sender, DocumentOpenedEventArgs e) void documentController_DocumentOpened(object sender, DocumentOpenedEventArgs e)

View File

@ -44,12 +44,15 @@
<!-- Project item tree --> <!-- Project item tree -->
<TreeView Grid.Row="2" Name="treeProjectItems"> <TreeView Grid.Row="2" Name="treeProjectItems">
<TreeView.ItemContainerStyle> <TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}"> <Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" /> <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" /> <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<EventSetter Event="Expanded" Handler="TreeViewItem_ExpandedOrCollapsed" /> <EventSetter Event="Expanded" Handler="TreeViewItem_ExpandedOrCollapsed" />
<EventSetter Event="Collapsed" Handler="TreeViewItem_ExpandedOrCollapsed" /> <EventSetter Event="Collapsed" Handler="TreeViewItem_ExpandedOrCollapsed" />
<EventSetter Event="PreviewMouseRightButtonDown" Handler="TreeViewItem_PreviewMouseRightButtonDown" />
<EventSetter Event="PreviewMouseDoubleClick" Handler="TreeViewItem_PreviewMouseDoubleClick" />
</Style> </Style>
</TreeView.ItemContainerStyle> </TreeView.ItemContainerStyle>
<TreeView.ItemTemplate> <TreeView.ItemTemplate>

View File

@ -1,6 +1,9 @@
using System; using System;
using System.Collections.Generic;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using RainmeterStudio.Core.Model; using RainmeterStudio.Core.Model;
using RainmeterStudio.Core.Utils; using RainmeterStudio.Core.Utils;
using RainmeterStudio.UI.Controller; using RainmeterStudio.UI.Controller;
@ -13,28 +16,31 @@ namespace RainmeterStudio.UI.Panels
/// </summary> /// </summary>
public partial class ProjectPanel : UserControl public partial class ProjectPanel : UserControl
{ {
private ProjectController _controller; private ProjectController _projectController;
public ProjectController Controller
public ProjectController ProjectController
{ {
get get
{ {
return _controller; return _projectController;
} }
set set
{ {
// Unsubscribe from old controller // Unsubscribe from old controller
if (_controller != null) if (_projectController != null)
{ {
Controller.ActiveProjectChanged -= Controller_ActiveProjectChanged; ProjectController.ActiveProjectChanged -= Controller_ActiveProjectChanged;
} }
// Set new project // Set new project
_controller = value; _projectController = value;
_controller.ActiveProjectChanged += Controller_ActiveProjectChanged; _projectController.ActiveProjectChanged += Controller_ActiveProjectChanged;
Refresh(); Refresh();
} }
} }
public DocumentController DocumentController { get; set; }
#region Commands #region Commands
public Command SyncWithActiveViewCommand { get; private set; } public Command SyncWithActiveViewCommand { get; private set; }
@ -60,7 +66,7 @@ namespace RainmeterStudio.UI.Panels
if (selected == null) if (selected == null)
{ {
return Controller.ActiveProject.Root; return ProjectController.ActiveProject.Root;
} }
else else
{ {
@ -85,7 +91,6 @@ namespace RainmeterStudio.UI.Panels
} }
} }
public ProjectPanel() public ProjectPanel()
{ {
InitializeComponent(); InitializeComponent();
@ -116,7 +121,7 @@ namespace RainmeterStudio.UI.Panels
treeProjectItems.Items.Clear(); treeProjectItems.Items.Clear();
// No project // No project
if (Controller == null || Controller.ActiveProject == null) if (ProjectController == null || ProjectController.ActiveProject == null)
{ {
this.IsEnabled = false; this.IsEnabled = false;
} }
@ -130,14 +135,14 @@ namespace RainmeterStudio.UI.Panels
if (toggleShowAllFiles.IsChecked.HasValue && toggleShowAllFiles.IsChecked.Value) if (toggleShowAllFiles.IsChecked.HasValue && toggleShowAllFiles.IsChecked.Value)
{ {
// Get directory name // Get directory name
string projectFolder = System.IO.Path.GetDirectoryName(Controller.ActiveProjectPath); string projectFolder = System.IO.Path.GetDirectoryName(ProjectController.ActiveProjectPath);
// Get folder tree // Get folder tree
refTree = DirectoryHelper.GetFolderTree(projectFolder); refTree = DirectoryHelper.GetFolderTree(projectFolder);
} }
else else
{ {
refTree = Controller.ActiveProject.Root; refTree = ProjectController.ActiveProject.Root;
} }
// Add tree to tree view // Add tree to tree view
@ -183,5 +188,106 @@ namespace RainmeterStudio.UI.Panels
// We can expand if the root is not expanded // We can expand if the root is not expanded
CanExpand = (!tree.IsExpanded); 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);
}
}
/// <summary>
/// Gets the default command (double click) for a specific reference
/// </summary>
/// <param name="reference">The reference</param>
/// <returns>The command</returns>
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<UIElement> 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);
}
} }
} }