diff --git a/RainmeterStudio.Core/Model/Project.cs b/RainmeterStudio.Core/Model/Project.cs index ee40b754..cf278100 100644 --- a/RainmeterStudio.Core/Model/Project.cs +++ b/RainmeterStudio.Core/Model/Project.cs @@ -109,40 +109,5 @@ namespace RainmeterStudio.Core.Model } #endregion - - #region Equals - - public override bool Equals(object obj) - { - Project other = obj as Project; - - if (other == null) - return false; - - bool res = String.Equals(Author, other.Author); - res &= Reference.Equals(AutoLoadFile, other.AutoLoadFile); - res &= Version.Equals(MinimumRainmeter, other.MinimumRainmeter); - res &= Version.Equals(MinimumWindows, other.MinimumWindows); - res &= String.Equals(Name, other.Name); - res &= Tree.Equals(Root, other.Root); - res &= Version.Equals(Version, other.Version); - - return res; - } - - public override int GetHashCode() - { - int hash = (Author == null) ? 0 : Author.GetHashCode(); - hash = hash * 7 + ((AutoLoadFile == null) ? 0 : AutoLoadFile.GetHashCode()); - hash = hash * 7 + ((MinimumRainmeter == null) ? 0 : MinimumRainmeter.GetHashCode()); - hash = hash * 7 + ((MinimumWindows == null) ? 0 : MinimumWindows.GetHashCode()); - hash = hash * 7 + ((Name == null) ? 0 : Name.GetHashCode()); - hash = hash * 7 + ((Root == null) ? 0 : Root.GetHashCode()); - hash = hash * 7 + ((Version == null) ? 0 : Version.GetHashCode()); - - return hash; - } - - #endregion } } diff --git a/RainmeterStudio.Core/Model/Reference.cs b/RainmeterStudio.Core/Model/Reference.cs index d78424a5..ee26f488 100644 --- a/RainmeterStudio.Core/Model/Reference.cs +++ b/RainmeterStudio.Core/Model/Reference.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using System.Xml.Serialization; @@ -9,27 +10,89 @@ namespace RainmeterStudio.Core.Model /// /// Reference to a file or folder /// - public class Reference + [DebuggerDisplay("ProjectPath = {ProjectPath}, StoragePath = {StoragePath}")] + public struct Reference { + private string[] _projectPath; + private string _storagePath; + /// /// Gets the name of the reference /// - public string Name { get; private set; } + public string Name + { + get + { + // Try to get the last item from the project path + if (_projectPath != null && _projectPath.Length > 0) + return _projectPath[_projectPath.Length - 1]; + + // None found, return null + return null; + } + } /// - /// Gets the path of the reference + /// Gets the path to the file on the disk. If reference is in a project, the path should be relative. /// - public string Path { get; private set; } + public string StoragePath + { + get + { + return _storagePath; + } + } + + /// + /// Gets the qualified path + /// + public string ProjectPath + { + get + { + if (_projectPath != null) + { + return _projectPath.Aggregate(String.Empty, (a, b) => a + "/" + b); + } + + return null; + } + } /// /// Initializes the reference /// /// Name of reference /// Path to item referenced - public Reference(string name, string path = null) + public Reference(string filePath, string projectPath = null) { - Name = name; - Path = path; + _storagePath = filePath; + + if (projectPath != null) + { + _projectPath = projectPath.Split('/').Skip(1).ToArray(); + } + else + { + _projectPath = null; + } + } + + /// + /// Checks if the reference points to a project item + /// + public bool IsInProject() + { + return (_projectPath != null); + } + + /// + /// Checks if the reference has a file on disk + /// + /// + public bool IsOnStorage() + { + return (_storagePath != null); } /// @@ -39,14 +102,20 @@ namespace RainmeterStudio.Core.Model /// True if objects are equal public override bool Equals(object obj) { - var other = obj as Reference; - - // Types are different, so not equal - if (other == null) - return false; + if (obj is Reference) + { + Reference other = (Reference)obj; - // Compare using string equals - return String.Equals(Name, other.Name) && String.Equals(Path, other.Path); + // 2 references are equal if they point to the same project item + if (_projectPath != null && other._projectPath != null) + return _projectPath.SequenceEqual(other._projectPath); + + // If there is no project item, compare storage paths + if (_projectPath == null && other._projectPath == null) + return String.Equals(_storagePath, other._storagePath); + } + + return false; } /// @@ -55,10 +124,28 @@ namespace RainmeterStudio.Core.Model /// Hash code public override int GetHashCode() { - int hash = (Name == null) ? 0 : Name.GetHashCode(); - hash = hash * 7 + ((Path == null) ? 0 : Path.GetHashCode()); + int hash = (_projectPath == null) ? 0 : _projectPath.GetHashCode(); + + if (_projectPath != null) + { + foreach (var item in _projectPath) + hash = hash * 7 + item.GetHashCode(); + } + else + { + hash = hash * 2113 + ((_storagePath == null) ? 0 : _storagePath.GetHashCode()); + } return hash; } + + /// + /// Gets the string representation of this reference + /// + /// + public override string ToString() + { + return ProjectPath; + } } } diff --git a/RainmeterStudio.Core/Storage/SerializableReference.cs b/RainmeterStudio.Core/Storage/SerializableReference.cs index 880fb4bc..156f31f1 100644 --- a/RainmeterStudio.Core/Storage/SerializableReference.cs +++ b/RainmeterStudio.Core/Storage/SerializableReference.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Xml.Serialization; using RainmeterStudio.Core.Model; +using RainmeterStudio.Core.Utils; namespace RainmeterStudio.Core.Storage { @@ -15,38 +17,38 @@ namespace RainmeterStudio.Core.Storage /// /// Gets or sets the name of the reference /// - [XmlElement("name")] - public string Name + [XmlElement("storagePath")] + public string StoragePath { get { - if (Reference != null) - return Reference.Name; + // Return only relative paths + if (Path.IsPathRooted(Reference.StoragePath)) + { + return PathHelper.GetRelativePath(Reference.StoragePath); + } - return null; + return Reference.StoragePath; } set { - Reference = new Reference(value, Path); + Reference = new Reference(value, ProjectPath); } } /// /// Gets or sets the path of the reference /// - [XmlElement("path")] - public string Path + [XmlElement("projectPath")] + public string ProjectPath { get { - if (Reference != null) - return Reference.Path; - - return null; + return Reference.ProjectPath; } set { - Reference = new Reference(Name, value); + Reference = new Reference(StoragePath, value); } } @@ -67,6 +69,10 @@ namespace RainmeterStudio.Core.Storage { } + /// + /// Initializes this serializable reference + /// + /// Reference to use public SerializableReference(Reference reference) { Reference = reference; diff --git a/RainmeterStudio.Core/Utils/PathHelper.cs b/RainmeterStudio.Core/Utils/PathHelper.cs index 0322815a..a9344e71 100644 --- a/RainmeterStudio.Core/Utils/PathHelper.cs +++ b/RainmeterStudio.Core/Utils/PathHelper.cs @@ -39,5 +39,37 @@ namespace RainmeterStudio.Core.Utils return true; } + + /// + /// Converts an absolute path to a path relative to current working directory + /// + /// Absolute path + /// Relative path + public static string GetRelativePath(string path) + { + return GetRelativePath(path, Environment.CurrentDirectory); + } + + /// + /// Converts an absolute path to a relative path + /// + /// Absolute path to file + /// Relative reference + /// Relative path + public static string GetRelativePath(string path, string relativeTo) + { + Uri pathUri = new Uri(path); + + // Folder must end in backslash + if (!relativeTo.EndsWith(Path.DirectorySeparatorChar.ToString())) + { + relativeTo += Path.DirectorySeparatorChar; + } + + Uri folderUri = new Uri(relativeTo); + Uri relativePath = pathUri.MakeRelativeUri(folderUri); + + return Uri.UnescapeDataString(relativeTo.ToString().Replace('/', Path.DirectorySeparatorChar)); + } } } diff --git a/RainmeterStudio.Tests/Storage/ProjectStorageTest.cs b/RainmeterStudio.Tests/Storage/ProjectStorageTest.cs index dfabe986..20dc06a2 100644 --- a/RainmeterStudio.Tests/Storage/ProjectStorageTest.cs +++ b/RainmeterStudio.Tests/Storage/ProjectStorageTest.cs @@ -39,8 +39,6 @@ namespace RainmeterStudio.Tests.Storage // Verify results Assert.IsNotNull(res); - Assert.AreEqual(project, res); - Assert.AreEqual(project.GetHashCode(), res.GetHashCode()); Assert.AreEqual(project.Author, res.Author); Assert.AreEqual(project.AutoLoadFile, res.AutoLoadFile); Assert.AreEqual(project.MinimumRainmeter, res.MinimumRainmeter); @@ -65,8 +63,6 @@ namespace RainmeterStudio.Tests.Storage // Test results Assert.IsNotNull(res); - Assert.AreEqual(project, res); - Assert.AreEqual(project.GetHashCode(), res.GetHashCode()); Assert.AreEqual(project.Author, res.Author); Assert.AreEqual(project.AutoLoadFile, res.AutoLoadFile); Assert.AreEqual(project.MinimumRainmeter, res.MinimumRainmeter); diff --git a/RainmeterStudio.TextEditor/TextEditorControl.xaml b/RainmeterStudio.TextEditor/TextEditorControl.xaml index 2fcd014e..c60181f2 100644 --- a/RainmeterStudio.TextEditor/TextEditorControl.xaml +++ b/RainmeterStudio.TextEditor/TextEditorControl.xaml @@ -6,6 +6,7 @@ mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> - + diff --git a/RainmeterStudio.TextEditor/TextEditorControl.xaml.cs b/RainmeterStudio.TextEditor/TextEditorControl.xaml.cs index a64e00ff..700795d9 100644 --- a/RainmeterStudio.TextEditor/TextEditorControl.xaml.cs +++ b/RainmeterStudio.TextEditor/TextEditorControl.xaml.cs @@ -32,5 +32,10 @@ namespace RainmeterStudio.TextEditorPlugin text.Text = txt.ToString(); } + + private void text_TextChanged(object sender, TextChangedEventArgs e) + { + _document.IsDirty = true; + } } } diff --git a/RainmeterStudio/Business/DocumentManager.cs b/RainmeterStudio/Business/DocumentManager.cs index b69e48c8..821a0f37 100644 --- a/RainmeterStudio/Business/DocumentManager.cs +++ b/RainmeterStudio/Business/DocumentManager.cs @@ -152,11 +152,11 @@ namespace RainmeterStudio.Business // Find a storage var storage = FindStorage(document); - if (document.Reference == null) + if (document.Reference.StoragePath == null) throw new ArgumentException("Reference cannot be empty"); // Save - storage.Write(document.Reference.Path, document); + storage.Write(document.Reference.StoragePath, document); // Clear dirty flag document.IsDirty = false; diff --git a/RainmeterStudio/RainmeterStudio.csproj b/RainmeterStudio/RainmeterStudio.csproj index 18be2ea4..7c4f5f74 100644 --- a/RainmeterStudio/RainmeterStudio.csproj +++ b/RainmeterStudio/RainmeterStudio.csproj @@ -90,6 +90,9 @@ + + CloseUnsavedDialog.xaml + CreateDocumentDialog.xaml @@ -104,6 +107,10 @@ + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/RainmeterStudio/Resources/Strings.Designer.cs b/RainmeterStudio/Resources/Strings.Designer.cs index a799170d..51ffe375 100644 --- a/RainmeterStudio/Resources/Strings.Designer.cs +++ b/RainmeterStudio/Resources/Strings.Designer.cs @@ -60,6 +60,33 @@ namespace RainmeterStudio.Resources { } } + /// + /// Looks up a localized string similar to The following files have unsaved changes:. + /// + public static string CloseUnsavedDialog_Message { + get { + return ResourceManager.GetString("CloseUnsavedDialog_Message", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Do you want to save these changes?. + /// + public static string CloseUnsavedDialog_Question { + get { + return ResourceManager.GetString("CloseUnsavedDialog_Question", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unsaved changes. + /// + public static string CloseUnsavedDialog_Title { + get { + return ResourceManager.GetString("CloseUnsavedDialog_Title", resourceCulture); + } + } + /// /// Looks up a localized string similar to _Close. /// @@ -339,6 +366,15 @@ namespace RainmeterStudio.Resources { } } + /// + /// Looks up a localized string similar to Do_n't save. + /// + public static string Dialog_DoNotSave { + get { + return ResourceManager.GetString("Dialog_DoNotSave", resourceCulture); + } + } + /// /// Looks up a localized string similar to All files. /// @@ -375,6 +411,15 @@ namespace RainmeterStudio.Resources { } } + /// + /// Looks up a localized string similar to _Save. + /// + public static string Dialog_Save { + get { + return ResourceManager.GetString("Dialog_Save", resourceCulture); + } + } + /// /// Looks up a localized string similar to _File. /// diff --git a/RainmeterStudio/Resources/Strings.resx b/RainmeterStudio/Resources/Strings.resx index 0963ae54..86bd0364 100644 --- a/RainmeterStudio/Resources/Strings.resx +++ b/RainmeterStudio/Resources/Strings.resx @@ -240,4 +240,19 @@ Select project path + + The following files have unsaved changes: + + + Do you want to save these changes? + + + Unsaved changes + + + Do_n't save + + + _Save + \ No newline at end of file diff --git a/RainmeterStudio/UI/Controller/DocumentController.cs b/RainmeterStudio/UI/Controller/DocumentController.cs index 56409bee..e626320f 100644 --- a/RainmeterStudio/UI/Controller/DocumentController.cs +++ b/RainmeterStudio/UI/Controller/DocumentController.cs @@ -34,6 +34,8 @@ namespace RainmeterStudio.UI.Controller public Command DocumentOpenCommand { get; private set; } + + #endregion /// @@ -65,6 +67,11 @@ namespace RainmeterStudio.UI.Controller ProjectManager.ActiveProjectChanged += new EventHandler((obj, e) => DocumentCreateCommand.NotifyCanExecuteChanged()); } + #region Document operations + + /// + /// Shows the new item dialog, and creates a new document + /// public void Create() { // Show dialog @@ -83,7 +90,7 @@ namespace RainmeterStudio.UI.Controller // Set the reference var name = dialog.SelectedName; - string folder = OwnerWindow.ProjectPanel.ActiveItem.Data.Path; + string folder = OwnerWindow.ProjectPanel.ActiveItem.Data.StoragePath; if (!Directory.Exists(folder)) folder = Path.GetDirectoryName(folder); @@ -97,12 +104,59 @@ namespace RainmeterStudio.UI.Controller OwnerWindow.ProjectPanel.ActiveItem.Add(reference); } - public void Create(IDocumentTemplate format) + /// + /// Saves the document opened in specified editor + /// + /// Editor + public void Save(IDocumentEditor editor) { - // Call manager - DocumentManager.Create(format); + if (!editor.AttachedDocument.Reference.IsOnStorage()) + { + SaveAs(editor); + return; + } + + // TODO } + public void SaveAs(IDocumentEditor editor) + { + // TODO + } + + /// + /// Closes an active document. + /// + /// The document editor attached + /// True if closed successfully + /// Shows the 'are you sure' prompt if there are unsaved edits. + public bool Close(IDocumentEditor editor) + { + // Show the 'are you sure' prompt if necesary + if (editor.AttachedDocument.IsDirty) + { + bool? res = CloseUnsavedDialog.ShowDialog(OwnerWindow, editor.AttachedDocument); + if (res.HasValue) + { + // Save + if (res.Value) + { + Save(editor); + } + } + else + { + return false; + } + } + + // Close + DocumentManager.Close(editor); + return true; + } + + #endregion + /// /// Gets a list of document templates view models /// diff --git a/RainmeterStudio/UI/Controller/IconProvider.cs b/RainmeterStudio/UI/Controller/IconProvider.cs index a0fbf2b6..24d64153 100644 --- a/RainmeterStudio/UI/Controller/IconProvider.cs +++ b/RainmeterStudio/UI/Controller/IconProvider.cs @@ -32,11 +32,11 @@ namespace RainmeterStudio.UI.Controller // Resource name string key = "ProjectItem"; - if (Directory.Exists(item.Path)) + if (Directory.Exists(item.StoragePath)) key += "Directory"; - else if (File.Exists(item.Path)) - key += "_" + Path.GetExtension(item.Path).Substring(1); + else if (File.Exists(item.StoragePath)) + key += "_" + Path.GetExtension(item.StoragePath).Substring(1); else key += "None"; @@ -55,10 +55,9 @@ namespace RainmeterStudio.UI.Controller { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { - var reference = value as Reference; - if (reference != null) + if (value is Reference) { - return IconProvider.GetProjectItemIcon(reference); + return IconProvider.GetProjectItemIcon((Reference)value); } return null; diff --git a/RainmeterStudio/UI/Dialogs/CloseUnsavedDialog.xaml b/RainmeterStudio/UI/Dialogs/CloseUnsavedDialog.xaml new file mode 100644 index 00000000..392798bc --- /dev/null +++ b/RainmeterStudio/UI/Dialogs/CloseUnsavedDialog.xaml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + +