From 1c4c7ccfb00c45f1111dc5e22febfb70a188996a Mon Sep 17 00:00:00 2001 From: Tiberiu Chibici Date: Mon, 28 Jul 2014 20:18:18 +0300 Subject: [PATCH] Work on project panel, trees, added settings --- RainmeterStudio/Model/Project.cs | 20 +- RainmeterStudio/Model/Tree.cs | 11 +- .../Properties/Settings.Designer.cs | 60 +++++- RainmeterStudio/Properties/Settings.settings | 24 ++- RainmeterStudio/RainmeterStudio.csproj | 7 + RainmeterStudio/Resources/Icons.Designer.cs | 13 +- RainmeterStudio/Resources/Icons.resx | 5 +- .../Resources/Icons/16/folder_project.png | Bin 0 -> 621 bytes RainmeterStudio/Resources/Strings.Designer.cs | 94 ++++++++- RainmeterStudio/Resources/Strings.resx | 34 +++- RainmeterStudio/Storage/SerializableTree.cs | 92 +++++++++ RainmeterStudio/UI/Command.cs | 77 +++++++- .../UI/Controller/DocumentController.cs | 5 +- RainmeterStudio/UI/Controller/IconProvider.cs | 6 +- .../UI/Controller/ProjectController.cs | 35 ++-- .../UI/Controller/SettingsProvider.cs | 49 +++++ RainmeterStudio/UI/MainWindow.xaml | 15 +- RainmeterStudio/UI/ProjectPanel.xaml | 8 +- RainmeterStudio/UI/ProjectPanel.xaml.cs | 178 ++++++++++-------- .../UI/ViewModel/ReferenceViewModel.cs | 125 ++++++++++++ RainmeterStudio/Utils/DirectoryHelper.cs | 19 ++ RainmeterStudio/Utils/TreeExtensions.cs | 162 ++++++++++++++++ RainmeterStudio/app.config | 26 ++- 23 files changed, 932 insertions(+), 133 deletions(-) create mode 100644 RainmeterStudio/Resources/Icons/16/folder_project.png create mode 100644 RainmeterStudio/Storage/SerializableTree.cs create mode 100644 RainmeterStudio/UI/Controller/SettingsProvider.cs create mode 100644 RainmeterStudio/UI/ViewModel/ReferenceViewModel.cs create mode 100644 RainmeterStudio/Utils/TreeExtensions.cs diff --git a/RainmeterStudio/Model/Project.cs b/RainmeterStudio/Model/Project.cs index edc71b5c..aba7f634 100644 --- a/RainmeterStudio/Model/Project.cs +++ b/RainmeterStudio/Model/Project.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml.Serialization; +using RainmeterStudio.Storage; namespace RainmeterStudio.Model { @@ -170,9 +171,26 @@ namespace RainmeterStudio.Model /// /// Gets or sets the root node /// - [XmlElement("root")] + [XmlIgnore] public Tree Root { get; set; } + /// + /// Gets or sets the serializable root node + /// + /// Warning: not efficient + [XmlElement("root")] + public SerializableTree SerializableRoot + { + get + { + return Root.AsSerializableTree(); + } + set + { + Root = value.AsTree(); + } + } + #endregion #region Constructor diff --git a/RainmeterStudio/Model/Tree.cs b/RainmeterStudio/Model/Tree.cs index 62466cf1..ac2ae0d6 100644 --- a/RainmeterStudio/Model/Tree.cs +++ b/RainmeterStudio/Model/Tree.cs @@ -7,13 +7,11 @@ using System.Xml.Serialization; namespace RainmeterStudio.Model { - public class Tree + public class Tree : IList> { - [XmlElement("data")] public T Data { get; set; } - [XmlArray("children"), XmlArrayItem("child")] - public ObservableCollection> Children { get; set; } + public ObservableCollection> Children { get; private set; } public Tree() { @@ -150,5 +148,10 @@ namespace RainmeterStudio.Model return hash; } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return Children.GetEnumerator(); + } } } diff --git a/RainmeterStudio/Properties/Settings.Designer.cs b/RainmeterStudio/Properties/Settings.Designer.cs index 43b346eb..287a887e 100644 --- a/RainmeterStudio/Properties/Settings.Designer.cs +++ b/RainmeterStudio/Properties/Settings.Designer.cs @@ -25,12 +25,66 @@ namespace RainmeterStudio.Properties { [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - public global::System.Windows.Input.KeyGestureConverter asdf { + [global::System.Configuration.DefaultSettingValueAttribute("Ctrl+Shift+N")] + [global::System.Configuration.SettingsManageabilityAttribute(global::System.Configuration.SettingsManageability.Roaming)] + public string ProjectCreateCommand_Shortcut { get { - return ((global::System.Windows.Input.KeyGestureConverter)(this["asdf"])); + return ((string)(this["ProjectCreateCommand_Shortcut"])); } set { - this["asdf"] = value; + this["ProjectCreateCommand_Shortcut"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("F5")] + [global::System.Configuration.SettingsManageabilityAttribute(global::System.Configuration.SettingsManageability.Roaming)] + public string ProjectPanel_RefreshCommand_Shortcut { + get { + return ((string)(this["ProjectPanel_RefreshCommand_Shortcut"])); + } + set { + this["ProjectPanel_RefreshCommand_Shortcut"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("Ctrl+N")] + [global::System.Configuration.SettingsManageabilityAttribute(global::System.Configuration.SettingsManageability.Roaming)] + public string DocumentCreateCommand_Shortcut { + get { + return ((string)(this["DocumentCreateCommand_Shortcut"])); + } + set { + this["DocumentCreateCommand_Shortcut"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("Ctrl+Shift+O")] + [global::System.Configuration.SettingsManageabilityAttribute(global::System.Configuration.SettingsManageability.Roaming)] + public string ProjectOpenCommand_Shortcut { + get { + return ((string)(this["ProjectOpenCommand_Shortcut"])); + } + set { + this["ProjectOpenCommand_Shortcut"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("Ctrl+W")] + [global::System.Configuration.SettingsManageabilityAttribute(global::System.Configuration.SettingsManageability.Roaming)] + public string DocumentCloseCommand_Shortcut { + get { + return ((string)(this["DocumentCloseCommand_Shortcut"])); + } + set { + this["DocumentCloseCommand_Shortcut"] = value; } } } diff --git a/RainmeterStudio/Properties/Settings.settings b/RainmeterStudio/Properties/Settings.settings index 8f2fd95d..83ff4d8d 100644 --- a/RainmeterStudio/Properties/Settings.settings +++ b/RainmeterStudio/Properties/Settings.settings @@ -1,7 +1,21 @@  - - - - - + + + + + Ctrl+Shift+N + + + F5 + + + Ctrl+N + + + Ctrl+Shift+O + + + Ctrl+W + + \ No newline at end of file diff --git a/RainmeterStudio/RainmeterStudio.csproj b/RainmeterStudio/RainmeterStudio.csproj index 98c62880..852c5e00 100644 --- a/RainmeterStudio/RainmeterStudio.csproj +++ b/RainmeterStudio/RainmeterStudio.csproj @@ -109,9 +109,11 @@ + + CreateDocumentDialog.xaml @@ -123,7 +125,9 @@ ProjectPanel.xaml + + Designer MSBuild:Compile @@ -248,6 +252,9 @@ + + + - - + + @@ -37,7 +38,15 @@ - + + + + + + + + diff --git a/RainmeterStudio/UI/ProjectPanel.xaml b/RainmeterStudio/UI/ProjectPanel.xaml index 06653854..310ea463 100644 --- a/RainmeterStudio/UI/ProjectPanel.xaml +++ b/RainmeterStudio/UI/ProjectPanel.xaml @@ -4,6 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:ctrl="clr-namespace:RainmeterStudio.UI.Controller" + xmlns:ui="clr-namespace:RainmeterStudio.UI" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> @@ -45,13 +46,16 @@ - + diff --git a/RainmeterStudio/UI/ProjectPanel.xaml.cs b/RainmeterStudio/UI/ProjectPanel.xaml.cs index 35aa7505..71fb6296 100644 --- a/RainmeterStudio/UI/ProjectPanel.xaml.cs +++ b/RainmeterStudio/UI/ProjectPanel.xaml.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.ComponentModel; using System.Linq; using System.Linq.Expressions; using System.Text; @@ -17,6 +18,7 @@ using RainmeterStudio.Interop; using RainmeterStudio.Model; using RainmeterStudio.Storage; using RainmeterStudio.UI.Controller; +using RainmeterStudio.UI.ViewModel; using RainmeterStudio.Utils; namespace RainmeterStudio.UI @@ -48,78 +50,50 @@ namespace RainmeterStudio.UI } } - private Command _syncWithActiveViewCommand; - public Command SyncWithActiveViewCommand + #region Commands + + public Command SyncWithActiveViewCommand { get; private set; } + + public Command RefreshCommand { get; private set; } + + public Command ExpandAllCommand { get; private set; } + + public Command CollapseAllCommand { get; private set; } + + public Command ShowAllFilesCommand { get; private set; } + + #endregion + + private bool _canExpand = false; + private bool CanExpand { get { - if (_syncWithActiveViewCommand == null) - { - _syncWithActiveViewCommand = new Command("ProjectPanel_SyncWithActiveViewCommand", SyncWithActiveView); - } - return _syncWithActiveViewCommand; + return _canExpand; + } + set + { + _canExpand = value; + + if (ExpandAllCommand != null) + ExpandAllCommand.NotifyCanExecuteChanged(); + + if (CollapseAllCommand != null) + CollapseAllCommand.NotifyCanExecuteChanged(); } } - private Command _refreshCommand; - public Command RefreshCommand - { - get - { - if (_refreshCommand == null) - { - _refreshCommand = new Command("ProjectPanel_RefreshCommand", SyncWithActiveView) - { - Shortcut = new KeyGesture(Key.F5) - }; - } - return _refreshCommand; - } - } - - private Command _expandAllCommand; - public Command ExpandAllCommand - { - get - { - if (_expandAllCommand == null) - { - _expandAllCommand = new Command("ProjectPanel_ExpandAllCommand", SyncWithActiveView); - } - return _expandAllCommand; - } - } - - private Command _collapseAllCommand; - public Command CollapseAllCommand - { - get - { - if (_collapseAllCommand == null) - { - _collapseAllCommand = new Command("ProjectPanel_CollapseAllCommand", SyncWithActiveView); - } - return _collapseAllCommand; - } - } - - private Command _showAllFilesCommand; - public Command ShowAllFilesCommand - { - get - { - if (_showAllFilesCommand == null) - { - _showAllFilesCommand = new Command("ProjectPanel_ShowAllFilesCommand", SyncWithActiveView); - } - return _showAllFilesCommand; - } - } public ProjectPanel() { InitializeComponent(); + SyncWithActiveViewCommand = new Command("ProjectPanel_SyncWithActiveViewCommand", SyncWithActiveView); + RefreshCommand = new Command("ProjectPanel_RefreshCommand", Refresh); + ExpandAllCommand = new Command("ProjectPanel_ExpandAllCommand", ExpandAll, () => _canExpand); + CollapseAllCommand = new Command("ProjectPanel_CollapseAllCommand", CollapseAll, () => !_canExpand); + ShowAllFilesCommand = new Command("ProjectPanel_ShowAllFilesCommand", Refresh); + this.DataContext = this; Refresh(); } @@ -144,34 +118,86 @@ namespace RainmeterStudio.UI { this.IsEnabled = true; - // Display all files in the project directory + // Get tree + Tree tree; if (toggleShowAllFiles.IsChecked.HasValue && toggleShowAllFiles.IsChecked.Value) { - string projectFolder = System.IO.Path.GetDirectoryName(Controller.ActiveProjectPath); - var tree = DirectoryHelper.GetFolderTree(projectFolder); - tree.Data = Controller.ActiveProject.Root.Data; - - treeProjectItems.Items.Clear(); - treeProjectItems.Items.Add(tree); + tree = GetAllFiles(); } - - // Display only the project items else { - treeProjectItems.Items.Clear(); - treeProjectItems.Items.Add(Controller.ActiveProject.Root); + tree = GetProjectItems(); } + + // Add tree to tree view + treeProjectItems.Items.Clear(); + treeProjectItems.Items.Add(tree); } } - private void toggleShowAllFiles_Checked(object sender, RoutedEventArgs e) + private Tree GetAllFiles() { - Refresh(); + // Get directory name + string projectFolder = System.IO.Path.GetDirectoryName(Controller.ActiveProjectPath); + + // Get folder tree + Tree refTree = DirectoryHelper.GetFolderTree(projectFolder); + refTree.Data = Controller.ActiveProject.Root.Data; + + // Remove the project file from the list + Tree project = refTree.First(x => DirectoryHelper.PathsEqual(x.Data.Path, Controller.ActiveProjectPath)); + refTree.Remove(project); + + // Transform to reference view model and return + return refTree.TransformData((data) => new ReferenceViewModel(data)); } - private void toggleShowAllFiles_Unchecked(object sender, RoutedEventArgs e) + private Tree GetProjectItems() { - Refresh(); + // Get project items + Tree refTree = Controller.ActiveProject.Root; + + // Transform to reference view model and return + return refTree.TransformData((data) => new ReferenceViewModel(data)); + } + + private void ExpandAll() + { + // Get tree + var tree = treeProjectItems.Items[0] as Tree; + if (tree == null) + return; + + // Expand all + tree.Apply((node) => node.Data.IsExpanded = true); + + // Set can expand property + CanExpand = false; + } + + private void CollapseAll() + { + // Get tree + var tree = treeProjectItems.Items[0] as Tree; + if (tree == null) + return; + + // Expand all + tree.Apply((node) => node.Data.IsExpanded = false); + + // Set can expand property + CanExpand = true; + } + + void TreeViewItem_ExpandedOrCollapsed(object sender, RoutedEventArgs e) + { + // Get tree + var tree = treeProjectItems.Items[0] as Tree; + if (tree == null) + return; + + // We can expand if the root is not expanded + CanExpand = (!tree.Data.IsExpanded); } } } diff --git a/RainmeterStudio/UI/ViewModel/ReferenceViewModel.cs b/RainmeterStudio/UI/ViewModel/ReferenceViewModel.cs new file mode 100644 index 00000000..0d9c389d --- /dev/null +++ b/RainmeterStudio/UI/ViewModel/ReferenceViewModel.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using RainmeterStudio.Model; + +namespace RainmeterStudio.UI.ViewModel +{ + /// + /// Contains the view model of a reference + /// + public class ReferenceViewModel : INotifyPropertyChanged + { + #region Properties + + /// + /// Gets the linked reference + /// + public Reference Reference { get; private set; } + + /// + /// Gets or sets the name + /// + public string Name + { + get + { + return Reference.Name; + } + set + { + Reference.Name = value; + + if (PropertyChanged != null) + PropertyChanged(this, new PropertyChangedEventArgs("Name")); + } + } + + /// + /// Gets or sets the path + /// + public string Path + { + get + { + return Reference.Path; + } + set + { + Reference.Path = value; + + if (PropertyChanged != null) + PropertyChanged(this, new PropertyChangedEventArgs("Path")); + } + } + + + private bool _isExpanded = true; + + /// + /// Gets or sets a property indicating if the tree view item is expanded + /// + public bool IsExpanded + { + get + { + return _isExpanded; + } + + set + { + _isExpanded = value; + + if (PropertyChanged != null) + PropertyChanged(this, new PropertyChangedEventArgs("IsExpanded")); + } + } + + private bool _isSelected; + + /// + /// Gets or sets a property indicating if the tree view item is selected + /// + public bool IsSelected + { + get + { + return _isSelected; + } + + set + { + _isSelected = value; + + if (PropertyChanged != null) + PropertyChanged(this, new PropertyChangedEventArgs("IsSelected")); + } + } + + #endregion + + #region Events + + /// + /// Event triggered when a property is changed + /// + public event PropertyChangedEventHandler PropertyChanged; + + #endregion + + #region Constructor + + /// + /// Creates a new instance of reference view model + /// + /// Reference + public ReferenceViewModel(Reference reference) + { + Reference = reference; + } + + #endregion + } +} diff --git a/RainmeterStudio/Utils/DirectoryHelper.cs b/RainmeterStudio/Utils/DirectoryHelper.cs index 084cc99e..141c2107 100644 --- a/RainmeterStudio/Utils/DirectoryHelper.cs +++ b/RainmeterStudio/Utils/DirectoryHelper.cs @@ -9,6 +9,11 @@ namespace RainmeterStudio.Utils { public static class DirectoryHelper { + /// + /// Gets a tree of the folder structure + /// + /// Folder + /// A tree public static Tree GetFolderTree(string folder) { // Build tree object @@ -28,5 +33,19 @@ namespace RainmeterStudio.Utils // Return tree return tree; } + + /// + /// Returns true if two paths are equal + /// + /// First path + /// Second path + /// True if the paths are equal + public static bool PathsEqual(string path1, string path2) + { + path1 = System.IO.Path.GetFullPath(path1); + path2 = System.IO.Path.GetFullPath(path2); + + return String.Equals(path1, path2, StringComparison.InvariantCultureIgnoreCase); + } } } diff --git a/RainmeterStudio/Utils/TreeExtensions.cs b/RainmeterStudio/Utils/TreeExtensions.cs new file mode 100644 index 00000000..fe437848 --- /dev/null +++ b/RainmeterStudio/Utils/TreeExtensions.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using RainmeterStudio.Model; + +namespace RainmeterStudio.Utils +{ + /// + /// Extension methods for trees + /// + public static class TreeExtensions + { + /// + /// Tree traversal orders + /// + public enum TreeTraversalOrder + { + BreadthFirst, + DepthFirst, + DepthFirstPreOrder = DepthFirst, + DepthFirstPostOrder + } + + /// + /// Traverses a tree + /// + /// Tree data type + /// Root node of tree + /// Traversal order + /// An enumeration of the nodes in the specified traverse order + public static IEnumerable> Traverse(this Tree root, TreeTraversalOrder order = TreeTraversalOrder.BreadthFirst) + { + if (order == TreeTraversalOrder.BreadthFirst) + return TraverseBF(root); + + else return TraverseDF(root, order); + } + + private static IEnumerable> TraverseDF(this Tree root, TreeTraversalOrder order) + { + // Preorder - return root first + if (order == TreeTraversalOrder.DepthFirstPreOrder) + yield return root; + + // Return children + foreach (var child in root.Children) + foreach (var node in TraverseDF(child, order)) + yield return node; + + // Postorder - return root last + if (order == TreeTraversalOrder.DepthFirstPostOrder) + yield return root; + } + + private static IEnumerable> TraverseBF(this Tree root) + { + // Create a queue containing the root + Queue> queue = new Queue>(); + queue.Enqueue(root); + + // While there are elements in the queue + while (queue.Count > 0) + { + // Return next node in tree + var node = queue.Dequeue(); + yield return node; + + // Enqueue node's children + foreach (var child in node.Children) + queue.Enqueue(child); + } + } + + /// + /// Applies an action to every node of the tree + /// + /// Tree data type + /// Root node of tree + /// Action to apply + /// Traversal order + public static void Apply(this Tree root, Action> action, TreeTraversalOrder order = TreeTraversalOrder.BreadthFirst) + { + // Safety check + if (action == null) + return; + + // Apply action + foreach (var node in Traverse(root, order)) + action(node); + } + + /// + /// Applies an action to every node of the tree + /// + /// Tree data type + /// Root node of tree + /// Action to apply + /// Traversal order + public static void ApplyToData(this Tree root, Action action, TreeTraversalOrder order = TreeTraversalOrder.BreadthFirst) + where T : class + { + // Safety check + if (action == null) + return; + + // Apply action + foreach (var node in Traverse(root, order)) + action(node.Data); + } + + /// + /// Rebuilds the tree by applying the specified transform function + /// + /// Data type of tree + /// Data type of rebuilt tree + /// The root node + /// The transform function + /// The transformed tree + public static Tree Transform(this Tree root, Func, Tree> transformFunction) + { + // Safety check + if (transformFunction == null) + throw new ArgumentNullException("Transform function cannot be null."); + + // Build root + Tree resRoot = transformFunction(root); + + // Add children + foreach (var node in root.Children) + resRoot.Children.Add(Transform(node, transformFunction)); + + // Return + return resRoot; + } + + /// + /// Rebuilds the tree by applying the specified transform function + /// + /// Data type of tree + /// Data type of rebuilt tree + /// The root node + /// The transform function + /// The transformed tree + public static Tree TransformData(this Tree root, Func transformFunction) + { + // Safety check + if (transformFunction == null) + throw new ArgumentNullException("Transform function cannot be null."); + + // Build root + Tree resRoot = new Tree(transformFunction(root.Data)); + + // Add children + foreach (var node in root.Children) + resRoot.Children.Add(TransformData(node, transformFunction)); + + // Return + return resRoot; + } + } +} diff --git a/RainmeterStudio/app.config b/RainmeterStudio/app.config index 57c0f2cb..8ed0c457 100644 --- a/RainmeterStudio/app.config +++ b/RainmeterStudio/app.config @@ -1,3 +1,27 @@ - + + +
+ + + + + + Ctrl+Shift+N + + + F5 + + + Ctrl+N + + + Ctrl+Shift+O + + + Ctrl+W + + + +