diff --git a/mRemoteNGTests/Tree/ConnectionTreeTests.cs b/mRemoteNGTests/Tree/ConnectionTreeTests.cs new file mode 100644 index 00000000..e2771009 --- /dev/null +++ b/mRemoteNGTests/Tree/ConnectionTreeTests.cs @@ -0,0 +1,29 @@ +using mRemoteNG.UI.Controls; +using NUnit.Framework; + + +namespace mRemoteNGTests.Tree +{ + public class ConnectionTreeTests + { + private ConnectionTree _connectionTree; + + [SetUp] + public void Setup() + { + _connectionTree = new ConnectionTree(); + } + + [TearDown] + public void Teardown() + { + _connectionTree.Dispose(); + } + + + [Test] + public void test() + { + } + } +} \ No newline at end of file diff --git a/mRemoteNGTests/mRemoteNGTests.csproj b/mRemoteNGTests/mRemoteNGTests.csproj index a8010efe..63e3ce76 100644 --- a/mRemoteNGTests/mRemoteNGTests.csproj +++ b/mRemoteNGTests/mRemoteNGTests.csproj @@ -101,7 +101,9 @@ - + + False + @@ -146,6 +148,7 @@ + diff --git a/mRemoteV1/App/Runtime.cs b/mRemoteV1/App/Runtime.cs index d99b8e5e..319f570b 100644 --- a/mRemoteV1/App/Runtime.cs +++ b/mRemoteV1/App/Runtime.cs @@ -42,8 +42,8 @@ namespace mRemoteNG.App public static SecureString EncryptionKey { get; set; } = new RootNodeInfo(RootNodeType.Connection).PasswordString.ConvertToSecureString(); public static ConnectionTreeModel ConnectionTreeModel { - get { return Windows.TreeForm.ConnectionTreeModel; } - set { Windows.TreeForm.ConnectionTreeModel = value; } + get { return Windows.TreeForm.ConnectionTree.ConnectionTreeModel; } + set { Windows.TreeForm.ConnectionTree.ConnectionTreeModel = value; } } #endregion @@ -237,7 +237,7 @@ namespace mRemoteNG.App // Load config connectionsLoader.ConnectionFileName = filename; ConnectionTreeModel = connectionsLoader.LoadConnections(false); - Windows.TreeForm.ConnectionTreeModel = ConnectionTreeModel; + Windows.TreeForm.ConnectionTree.ConnectionTreeModel = ConnectionTreeModel; } catch (Exception ex) { @@ -288,7 +288,7 @@ namespace mRemoteNG.App connectionsLoader.UseDatabase = Settings.Default.UseSQLServer; ConnectionTreeModel = connectionsLoader.LoadConnections(false); - Windows.TreeForm.ConnectionTreeModel = ConnectionTreeModel; + Windows.TreeForm.ConnectionTree.ConnectionTreeModel = ConnectionTreeModel; if (Settings.Default.UseSQLServer) { diff --git a/mRemoteV1/UI/Controls/ConnectionTree.cs b/mRemoteV1/UI/Controls/ConnectionTree.cs index a94326f3..e3895eb7 100644 --- a/mRemoteV1/UI/Controls/ConnectionTree.cs +++ b/mRemoteV1/UI/Controls/ConnectionTree.cs @@ -1,8 +1,17 @@ -using System.Collections.Generic; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; using System.Linq; +using System.Windows.Forms; using BrightIdeasSoftware; using mRemoteNG.App; +using mRemoteNG.Config.Putty; using mRemoteNG.Connection; +using mRemoteNG.Container; +using mRemoteNG.Tools; +using mRemoteNG.Tree; using mRemoteNG.Tree.Root; @@ -10,12 +19,451 @@ namespace mRemoteNG.UI.Controls { public partial class ConnectionTree : TreeListView { + private ConnectionTreeModel _connectionTreeModel; + private readonly ConnectionTreeDragAndDropHandler _dragAndDropHandler = new ConnectionTreeDragAndDropHandler(); + private readonly PuttySessionsManager _puttySessionsManager = PuttySessionsManager.Instance; + private OLVColumn _olvNameColumn; + private ImageList imgListTree; + + public ConnectionInfo SelectedNode => (ConnectionInfo)SelectedObject; + public NodeSearcher NodeSearcher { get; private set; } + + public ConnectionTreeModel ConnectionTreeModel + { + get { return _connectionTreeModel; } + set + { + _connectionTreeModel = value; + PopulateTreeView(); + } + } + public ConnectionTree() { InitializeComponent(); + SetupConnectionTreeView(); + } + + #region ConnectionTree Setup + private void SetupConnectionTreeView() + { + CreateNameColumn(); + CreateImageList(); + FillImageList(); + LinkModelToView(); + SetupDropSink(); + SetEventHandlers(); + } + + private void CreateNameColumn() + { + _olvNameColumn = new OLVColumn + { + AspectName = "Name", + FillsFreeSpace = true, + IsButton = true, + AspectGetter = item => ((ConnectionInfo)item).Name, + ImageGetter = ConnectionImageGetter + }; + Columns.Add(_olvNameColumn); + AllColumns.Add(_olvNameColumn); + } + + private void CreateImageList() + { + imgListTree = new ImageList(this.components) + { + ColorDepth = ColorDepth.Depth32Bit, + ImageSize = new System.Drawing.Size(16, 16), + TransparentColor = System.Drawing.Color.Transparent + }; + SmallImageList = imgListTree; + } + + private void FillImageList() + { + try + { + imgListTree.Images.Add(Resources.Root); + imgListTree.Images.SetKeyName(0, "Root"); + imgListTree.Images.Add(Resources.Folder); + imgListTree.Images.SetKeyName(1, "Folder"); + imgListTree.Images.Add(Resources.Play); + imgListTree.Images.SetKeyName(2, "Play"); + imgListTree.Images.Add(Resources.Pause); + imgListTree.Images.SetKeyName(3, "Pause"); + imgListTree.Images.Add(Resources.PuttySessions); + imgListTree.Images.SetKeyName(4, "PuttySessions"); + } + catch (Exception ex) + { + Runtime.MessageCollector.AddExceptionStackTrace("FillImageList (UI.Window.ConnectionTreeWindow) failed", ex); + } + } + + private void LinkModelToView() + { + CanExpandGetter = item => + { + var itemAsContainer = item as ContainerInfo; + return itemAsContainer?.Children.Count > 0; + }; + ChildrenGetter = item => ((ContainerInfo)item).Children; + //ContextMenuStrip = _contextMenu; + } + + private static object ConnectionImageGetter(object rowObject) + { + if (rowObject is RootPuttySessionsNodeInfo) return "PuttySessions"; + if (rowObject is RootNodeInfo) return "Root"; + if (rowObject is ContainerInfo) return "Folder"; + var connection = rowObject as ConnectionInfo; + if (connection == null) return ""; + return connection.OpenConnections.Count > 0 ? "Play" : "Pause"; + } + + private void SetupDropSink() + { + DropSink = new SimpleDropSink(); + var dropSink = (SimpleDropSink)DropSink; + dropSink.CanDropBetween = true; + } + + private void SetEventHandlers() + { + Collapsed += (sender, args) => + { + var container = args.Model as ContainerInfo; + if (container != null) + container.IsExpanded = false; + }; + Expanded += (sender, args) => + { + var container = args.Model as ContainerInfo; + if (container != null) + container.IsExpanded = true; + }; + SelectionChanged += tvConnections_AfterSelect; + CellClick += tvConnections_NodeMouseSingleClick; + CellClick += tvConnections_NodeMouseDoubleClick; + CellToolTipShowing += tvConnections_CellToolTipShowing; + ModelCanDrop += _dragAndDropHandler.HandleEvent_ModelCanDrop; + ModelDropped += _dragAndDropHandler.HandleEvent_ModelDropped; + } + + private void PopulateTreeView() + { + UnregisterModelUpdateHandlers(); + SetObjects(ConnectionTreeModel.RootNodes); + RegisterModelUpdateHandlers(); + NodeSearcher = new NodeSearcher(ConnectionTreeModel); + ExpandPreviouslyOpenedFolders(); + ExpandRootConnectionNode(); + OpenConnectionsFromLastSession(); + } + + private void RegisterModelUpdateHandlers() + { + _puttySessionsManager.PuttySessionsCollectionChanged += OnPuttySessionsCollectionChanged; + ConnectionTreeModel.CollectionChanged += HandleCollectionChanged; + ConnectionTreeModel.PropertyChanged += HandleCollectionPropertyChanged; + } + + private void UnregisterModelUpdateHandlers() + { + _puttySessionsManager.PuttySessionsCollectionChanged -= OnPuttySessionsCollectionChanged; + ConnectionTreeModel.CollectionChanged -= HandleCollectionChanged; + ConnectionTreeModel.PropertyChanged -= HandleCollectionPropertyChanged; + } + + private void OnPuttySessionsCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) + { + RefreshTreeObjects(GetRootPuttyNodes().ToList()); + } + + private void HandleCollectionPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs) + { + //TODO for some reason property changed events are getting triggered twice for each changed property. should be just once. cant find source of duplication + var property = propertyChangedEventArgs.PropertyName; + if (property != "Name" && property != "OpenConnections") return; + var senderAsConnectionInfo = sender as ConnectionInfo; + if (senderAsConnectionInfo != null) + RefreshTreeObject(senderAsConnectionInfo); + } + + private void ExpandPreviouslyOpenedFolders() + { + var containerList = ConnectionTreeModel.GetRecursiveChildList(GetRootConnectionNode()).OfType(); + var previouslyExpandedNodes = containerList.Where(container => container.IsExpanded); + ExpandedObjects = previouslyExpandedNodes; + this.InvokeRebuildAll(true); + } + + private void OpenConnectionsFromLastSession() + { + if (!Settings.Default.OpenConsFromLastSession || Settings.Default.NoReconnect) return; + var connectionInfoList = GetRootConnectionNode().GetRecursiveChildList().Where(node => !(node is ContainerInfo)); + var previouslyOpenedConnections = connectionInfoList.Where(item => item.PleaseConnect); + foreach (var connectionInfo in previouslyOpenedConnections) + { + ConnectionInitiator.OpenConnection(connectionInfo); + } + } + #endregion + + #region ConnectionTree + public void DeleteSelectedNode() + { + if (SelectedNode is RootNodeInfo || SelectedNode is PuttySessionInfo) return; + if (!UserConfirmsDeletion()) return; + ConnectionTreeModel.DeleteNode(SelectedNode); + Runtime.SaveConnectionsAsync(); + } + + private bool UserConfirmsDeletion() + { + var selectedNodeAsContainer = SelectedNode as ContainerInfo; + if (selectedNodeAsContainer != null) + return selectedNodeAsContainer.HasChildren() + ? UserConfirmsNonEmptyFolderDeletion() + : UserConfirmsEmptyFolderDeletion(); + return UserConfirmsConnectionDeletion(); + } + + private bool UserConfirmsEmptyFolderDeletion() + { + var messagePrompt = string.Format(Language.strConfirmDeleteNodeFolder, SelectedNode.Name); + return PromptUser(messagePrompt); + } + + private bool UserConfirmsNonEmptyFolderDeletion() + { + var messagePrompt = string.Format(Language.strConfirmDeleteNodeFolderNotEmpty, SelectedNode.Name); + return PromptUser(messagePrompt); + } + + private bool UserConfirmsConnectionDeletion() + { + var messagePrompt = string.Format(Language.strConfirmDeleteNodeConnection, SelectedNode.Name); + return PromptUser(messagePrompt); + } + + private static bool PromptUser(string promptMessage) + { + var msgBoxResponse = MessageBox.Show(promptMessage, Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question); + return (msgBoxResponse == DialogResult.Yes); + } + + private void HandleCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) + { + var senderAsContainerInfo = sender as ContainerInfo; + // ReSharper disable once SwitchStatementMissingSomeCases + switch (args?.Action) + { + case NotifyCollectionChangedAction.Add: + var childList = senderAsContainerInfo?.Children; + ConnectionInfo otherChild = null; + if (childList?.Count > 1) + otherChild = childList.First(child => !args.NewItems.Contains(child)); + RefreshTreeObject(otherChild ?? senderAsContainerInfo); + break; + case NotifyCollectionChangedAction.Remove: + RefreshTreeObjects(args.OldItems); + break; + case NotifyCollectionChangedAction.Move: + RefreshTreeObjects(args.OldItems); + break; + case NotifyCollectionChangedAction.Reset: + RefreshTreeObject(senderAsContainerInfo); + break; + case NotifyCollectionChangedAction.Replace: + break; + case null: + break; + } + } + + private void RefreshTreeObject(ConnectionInfo modelObject) + { + RefreshObject(modelObject); + } + + private void RefreshTreeObjects(IList modelObjects) + { + RefreshObjects(modelObjects); + } + + public void AddConnection() + { + try + { + AddNode(new ConnectionInfo()); + } + catch (Exception ex) + { + Runtime.MessageCollector.AddExceptionStackTrace("UI.Window.Tree.AddConnection() failed.", ex); + } + } + + public void AddFolder() + { + try + { + AddNode(new ContainerInfo()); + } + catch (Exception ex) + { + Runtime.MessageCollector.AddExceptionStackTrace(Language.strErrorAddFolderFailed, ex); + } + } + + private void AddNode(ConnectionInfo newNode) + { + if (SelectedNode == null) return; + DefaultConnectionInfo.Instance.SaveTo(newNode); + DefaultConnectionInheritance.Instance.SaveTo(newNode.Inheritance); + var selectedContainer = SelectedNode as ContainerInfo; + var parent = selectedContainer ?? SelectedNode?.Parent; + newNode.SetParent(parent); + Expand(parent); + SelectObject(newNode); + EnsureModelVisible(newNode); + } + + public void DisconnectConnection(ConnectionInfo connectionInfo) + { + try + { + if (connectionInfo == null) return; + var nodeAsContainer = connectionInfo as ContainerInfo; + if (nodeAsContainer != null) + { + foreach (var child in nodeAsContainer.Children) + { + for (var i = 0; i <= child.OpenConnections.Count - 1; i++) + { + child.OpenConnections[i].Disconnect(); + } + } + } + else + { + for (var i = 0; i <= connectionInfo.OpenConnections.Count - 1; i++) + { + connectionInfo.OpenConnections[i].Disconnect(); + } + } + } + catch (Exception ex) + { + Runtime.MessageCollector.AddExceptionStackTrace("DisconnectConnection (UI.Window.ConnectionTreeWindow) failed", ex); + } + } + + public void SshTransferFile() + { + try + { + Windows.Show(WindowType.SSHTransfer); + Windows.SshtransferForm.Hostname = SelectedNode.Hostname; + Windows.SshtransferForm.Username = SelectedNode.Username; + Windows.SshtransferForm.Password = SelectedNode.Password; + Windows.SshtransferForm.Port = Convert.ToString(SelectedNode.Port); + } + catch (Exception ex) + { + Runtime.MessageCollector.AddExceptionStackTrace("SSHTransferFile (UI.Window.ConnectionTreeWindow) failed", ex); + } + } + + public void StartExternalApp(ExternalTool externalTool) + { + try + { + if (SelectedNode.GetTreeNodeType() == TreeNodeType.Connection | SelectedNode.GetTreeNodeType() == TreeNodeType.PuttySession) + externalTool.Start(SelectedNode); + } + catch (Exception ex) + { + Runtime.MessageCollector.AddExceptionStackTrace("cMenTreeToolsExternalAppsEntry_Click failed (UI.Window.ConnectionTreeWindow)", ex); + } + } + + private void ExpandRootConnectionNode() + { + var rootConnectionNode = GetRootConnectionNode(); + this.InvokeExpand(rootConnectionNode); + } + + public void EnsureRootNodeVisible() + { + EnsureModelVisible(GetRootConnectionNode()); + } + + private void tvConnections_AfterSelect(object sender, EventArgs e) + { + try + { + Windows.ConfigForm.SelectedTreeNode = SelectedNode; + } + catch (Exception ex) + { + Runtime.MessageCollector.AddExceptionStackTrace("tvConnections_AfterSelect (UI.Window.ConnectionTreeWindow) failed", ex); + } + } + + private void tvConnections_NodeMouseSingleClick(object sender, CellClickEventArgs e) + { + try + { + if (e.ClickCount > 1) return; + var clickedNode = e.Model as ConnectionInfo; + + if (clickedNode == null) return; + if (clickedNode.GetTreeNodeType() != TreeNodeType.Connection && clickedNode.GetTreeNodeType() != TreeNodeType.PuttySession) return; + if (Settings.Default.SingleClickOnConnectionOpensIt) + ConnectionInitiator.OpenConnection(SelectedNode); + + if (Settings.Default.SingleClickSwitchesToOpenConnection) + ConnectionInitiator.SwitchToOpenConnection(SelectedNode); + } + catch (Exception ex) + { + Runtime.MessageCollector.AddExceptionStackTrace("tvConnections_NodeMouseClick (UI.Window.ConnectionTreeWindow) failed", ex); + } + } + + private void tvConnections_NodeMouseDoubleClick(object sender, CellClickEventArgs e) + { + if (e.ClickCount < 2) return; + var clickedNodeAsContainer = e.Model as ContainerInfo; + if (clickedNodeAsContainer != null) + { + ToggleExpansion(clickedNodeAsContainer); + } + + var clickedNode = e.Model as ConnectionInfo; + if (clickedNode?.GetTreeNodeType() == TreeNodeType.Connection | + clickedNode?.GetTreeNodeType() == TreeNodeType.PuttySession) + { + ConnectionInitiator.OpenConnection(SelectedNode); + } + } + + private void tvConnections_CellToolTipShowing(object sender, ToolTipShowingEventArgs e) + { + try + { + var nodeProducingTooltip = (ConnectionInfo)e.Model; + e.Text = nodeProducingTooltip.Description; + } + catch (Exception ex) + { + Runtime.MessageCollector.AddExceptionStackTrace("tvConnections_MouseMove (UI.Window.ConnectionTreeWindow) failed", ex); + } } public RootNodeInfo GetRootConnectionNode() @@ -40,5 +488,6 @@ namespace mRemoteNG.UI.Controls SelectedItem.BeginEdit(); Runtime.SaveConnectionsAsync(); } + #endregion } } \ No newline at end of file diff --git a/mRemoteV1/UI/Forms/frmMain.cs b/mRemoteV1/UI/Forms/frmMain.cs index 5d632e74..20a7f3f1 100644 --- a/mRemoteV1/UI/Forms/frmMain.cs +++ b/mRemoteV1/UI/Forms/frmMain.cs @@ -494,13 +494,13 @@ namespace mRemoteNG.UI.Forms private void mMenFileNewConnection_Click(object sender, EventArgs e) { - ConnectionTreeWindow.AddConnection(); + ConnectionTreeWindow.ConnectionTree.AddConnection(); Runtime.SaveConnectionsAsync(); } private void mMenFileNewFolder_Click(object sender, EventArgs e) { - ConnectionTreeWindow.AddFolder(); + ConnectionTreeWindow.ConnectionTree.AddFolder(); Runtime.SaveConnectionsAsync(); } @@ -546,19 +546,19 @@ namespace mRemoteNG.UI.Forms private void mMenFileDelete_Click(object sender, EventArgs e) { - ConnectionTreeWindow.DeleteSelectedNode(); + ConnectionTreeWindow.ConnectionTree.DeleteSelectedNode(); Runtime.SaveConnectionsAsync(); } private void mMenFileRename_Click(object sender, EventArgs e) { - ConnectionTreeWindow.RenameSelectedNode(); + ConnectionTreeWindow.ConnectionTree.RenameSelectedNode(); Runtime.SaveConnectionsAsync(); } private void mMenFileDuplicate_Click(object sender, EventArgs e) { - ConnectionTreeWindow.DuplicateSelectedNode(); + ConnectionTreeWindow.ConnectionTree.DuplicateSelectedNode(); Runtime.SaveConnectionsAsync(); } diff --git a/mRemoteV1/UI/Window/ConnectionTreeWindow.Designer.cs b/mRemoteV1/UI/Window/ConnectionTreeWindow.Designer.cs index 18666dda..abb971af 100644 --- a/mRemoteV1/UI/Window/ConnectionTreeWindow.Designer.cs +++ b/mRemoteV1/UI/Window/ConnectionTreeWindow.Designer.cs @@ -7,7 +7,6 @@ namespace mRemoteNG.UI.Window #region Windows Form Designer generated code internal System.Windows.Forms.TextBox txtSearch; internal System.Windows.Forms.Panel pnlConnections; - internal System.Windows.Forms.ImageList imgListTree; internal System.Windows.Forms.MenuStrip msMain; internal System.Windows.Forms.ToolStripMenuItem mMenView; internal System.Windows.Forms.ToolStripMenuItem mMenViewExpandAllFolders; @@ -17,13 +16,10 @@ namespace mRemoteNG.UI.Window internal System.Windows.Forms.ToolStripMenuItem mMenAddConnection; internal System.Windows.Forms.ToolStripMenuItem mMenAddFolder; public System.Windows.Forms.TreeView tvConnections; - public mRemoteNG.UI.Controls.ConnectionTree olvConnections; private void InitializeComponent() { this.components = new System.ComponentModel.Container(); this.olvConnections = new mRemoteNG.UI.Controls.ConnectionTree(); - this.olvNameColumn = ((BrightIdeasSoftware.OLVColumn)(new BrightIdeasSoftware.OLVColumn())); - this.imgListTree = new System.Windows.Forms.ImageList(this.components); this.pnlConnections = new System.Windows.Forms.Panel(); this.PictureBox1 = new System.Windows.Forms.PictureBox(); this.txtSearch = new System.Windows.Forms.TextBox(); @@ -42,15 +38,12 @@ namespace mRemoteNG.UI.Window // // olvConnections // - this.olvConnections.AllColumns.Add(this.olvNameColumn); this.olvConnections.AllowDrop = true; this.olvConnections.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); this.olvConnections.BorderStyle = System.Windows.Forms.BorderStyle.None; this.olvConnections.CellEditUseWholeCell = false; - this.olvConnections.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { - this.olvNameColumn}); this.olvConnections.Cursor = System.Windows.Forms.Cursors.Default; this.olvConnections.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.None; this.olvConnections.HideSelection = false; @@ -64,7 +57,6 @@ namespace mRemoteNG.UI.Window this.olvConnections.SelectedForeColor = System.Drawing.SystemColors.HighlightText; this.olvConnections.ShowGroups = false; this.olvConnections.Size = new System.Drawing.Size(192, 410); - this.olvConnections.SmallImageList = this.imgListTree; this.olvConnections.TabIndex = 20; this.olvConnections.UnfocusedSelectedBackColor = System.Drawing.SystemColors.Highlight; this.olvConnections.UnfocusedSelectedForeColor = System.Drawing.SystemColors.HighlightText; @@ -72,18 +64,6 @@ namespace mRemoteNG.UI.Window this.olvConnections.View = System.Windows.Forms.View.Details; this.olvConnections.VirtualMode = true; // - // olvNameColumn - // - this.olvNameColumn.AspectName = "Name"; - this.olvNameColumn.FillsFreeSpace = true; - this.olvNameColumn.IsButton = true; - // - // imgListTree - // - this.imgListTree.ColorDepth = System.Windows.Forms.ColorDepth.Depth32Bit; - this.imgListTree.ImageSize = new System.Drawing.Size(16, 16); - this.imgListTree.TransparentColor = System.Drawing.Color.Transparent; - // // pnlConnections // this.pnlConnections.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) @@ -214,6 +194,6 @@ namespace mRemoteNG.UI.Window #endregion private System.ComponentModel.IContainer components; - private BrightIdeasSoftware.OLVColumn olvNameColumn; + private Controls.ConnectionTree olvConnections; } } diff --git a/mRemoteV1/UI/Window/ConnectionTreeWindow.cs b/mRemoteV1/UI/Window/ConnectionTreeWindow.cs index 484e3742..0056b9bb 100644 --- a/mRemoteV1/UI/Window/ConnectionTreeWindow.cs +++ b/mRemoteV1/UI/Window/ConnectionTreeWindow.cs @@ -3,16 +3,10 @@ using mRemoteNG.Connection; using mRemoteNG.Container; using mRemoteNG.Tree; using System; -using System.Collections; -using System.Collections.Specialized; using System.ComponentModel; using System.Drawing; -using System.Linq; using System.Windows.Forms; -using BrightIdeasSoftware; -using mRemoteNG.Config.Putty; using mRemoteNG.Tools; -using mRemoteNG.Tree.Root; using mRemoteNG.UI.Controls; using WeifenLuo.WinFormsUI.Docking; @@ -21,22 +15,14 @@ namespace mRemoteNG.UI.Window { public partial class ConnectionTreeWindow { - private ConnectionTreeModel _connectionTreeModel; - private readonly ConnectionTreeDragAndDropHandler _dragAndDropHandler = new ConnectionTreeDragAndDropHandler(); - private NodeSearcher _nodeSearcher; private readonly ConnectionContextMenu _contextMenu = new ConnectionContextMenu(); - private readonly PuttySessionsManager _puttySessionsManager = PuttySessionsManager.Instance; public ConnectionInfo SelectedNode => olvConnections.SelectedNode; - public ConnectionTreeModel ConnectionTreeModel + public ConnectionTree ConnectionTree { - get { return _connectionTreeModel; } - set - { - _connectionTreeModel = value; - PopulateTreeView(); - } + get { return olvConnections; } + set { olvConnections = value; } } public ConnectionTreeWindow(DockContent panel) @@ -44,7 +30,10 @@ namespace mRemoteNG.UI.Window WindowType = WindowType.Tree; DockPnl = panel; InitializeComponent(); - SetupConnectionTreeView(); + olvConnections.ContextMenuStrip = _contextMenu; + SetMenuEventHandlers(); + SetContextMenuEventHandlers(); + SetConnectionTreeEventHandlers(); } #region Form Stuff @@ -88,413 +77,14 @@ namespace mRemoteNG.UI.Window } #endregion - #region ConnectionTree Setup - private void SetupConnectionTreeView() + #region ConnectionTree + private void SetConnectionTreeEventHandlers() { - FillImageList(); - LinkModelToView(); - SetupDropSink(); - SetEventHandlers(); - } - - private void FillImageList() - { - try - { - imgListTree.Images.Add(Resources.Root); - imgListTree.Images.SetKeyName(0, "Root"); - imgListTree.Images.Add(Resources.Folder); - imgListTree.Images.SetKeyName(1, "Folder"); - imgListTree.Images.Add(Resources.Play); - imgListTree.Images.SetKeyName(2, "Play"); - imgListTree.Images.Add(Resources.Pause); - imgListTree.Images.SetKeyName(3, "Pause"); - imgListTree.Images.Add(Resources.PuttySessions); - imgListTree.Images.SetKeyName(4, "PuttySessions"); - } - catch (Exception ex) - { - Runtime.MessageCollector.AddExceptionStackTrace("FillImageList (UI.Window.ConnectionTreeWindow) failed", ex); - } - } - - private void LinkModelToView() - { - olvNameColumn.AspectGetter = item => ((ConnectionInfo)item).Name; - olvNameColumn.ImageGetter = ConnectionImageGetter; - olvConnections.CanExpandGetter = item => - { - var itemAsContainer = item as ContainerInfo; - return itemAsContainer?.Children.Count > 0; - }; - olvConnections.ChildrenGetter = item => ((ContainerInfo)item).Children; - olvConnections.ContextMenuStrip = _contextMenu; - } - - private static object ConnectionImageGetter(object rowObject) - { - if (rowObject is RootPuttySessionsNodeInfo) return "PuttySessions"; - if (rowObject is RootNodeInfo) return "Root"; - if (rowObject is ContainerInfo) return "Folder"; - var connection = rowObject as ConnectionInfo; - if (connection == null) return ""; - return connection.OpenConnections.Count > 0 ? "Play" : "Pause"; - } - - private void SetupDropSink() - { - var dropSink = (SimpleDropSink)olvConnections.DropSink; - dropSink.CanDropBetween = true; - } - - private void SetEventHandlers() - { - SetTreeEventHandlers(); - SetContextMenuEventHandlers(); - SetMenuEventHandlers(); - } - - private void SetTreeEventHandlers() - { - olvConnections.Collapsed += (sender, args) => - { - var container = args.Model as ContainerInfo; - if (container != null) - container.IsExpanded = false; - }; - olvConnections.Expanded += (sender, args) => - { - var container = args.Model as ContainerInfo; - if (container != null) - container.IsExpanded = true; - }; olvConnections.BeforeLabelEdit += tvConnections_BeforeLabelEdit; olvConnections.AfterLabelEdit += tvConnections_AfterLabelEdit; - olvConnections.SelectionChanged += tvConnections_AfterSelect; - olvConnections.CellClick += tvConnections_NodeMouseSingleClick; - olvConnections.CellClick += tvConnections_NodeMouseDoubleClick; - olvConnections.CellToolTipShowing += tvConnections_CellToolTipShowing; - olvConnections.ModelCanDrop += _dragAndDropHandler.HandleEvent_ModelCanDrop; - olvConnections.ModelDropped += _dragAndDropHandler.HandleEvent_ModelDropped; olvConnections.KeyDown += tvConnections_KeyDown; olvConnections.KeyPress += tvConnections_KeyPress; } - - private void PopulateTreeView() - { - UnregisterModelUpdateHandlers(); - olvConnections.SetObjects(ConnectionTreeModel.RootNodes); - RegisterModelUpdateHandlers(); - _nodeSearcher = new NodeSearcher(ConnectionTreeModel); - ExpandPreviouslyOpenedFolders(); - ExpandRootConnectionNode(); - OpenConnectionsFromLastSession(); - } - - private void RegisterModelUpdateHandlers() - { - _puttySessionsManager.PuttySessionsCollectionChanged += OnPuttySessionsCollectionChanged; - ConnectionTreeModel.CollectionChanged += HandleCollectionChanged; - ConnectionTreeModel.PropertyChanged += HandleCollectionPropertyChanged; - } - - private void UnregisterModelUpdateHandlers() - { - _puttySessionsManager.PuttySessionsCollectionChanged -= OnPuttySessionsCollectionChanged; - ConnectionTreeModel.CollectionChanged -= HandleCollectionChanged; - ConnectionTreeModel.PropertyChanged -= HandleCollectionPropertyChanged; - } - - private void OnPuttySessionsCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) - { - RefreshTreeObjects(olvConnections.GetRootPuttyNodes().ToList()); - } - - private void HandleCollectionPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs) - { - //TODO for some reason property changed events are getting triggered twice for each changed property. should be just once. cant find source of duplication - var property = propertyChangedEventArgs.PropertyName; - if (property != "Name" && property != "OpenConnections") return; - var senderAsConnectionInfo = sender as ConnectionInfo; - if (senderAsConnectionInfo != null) - RefreshTreeObject(senderAsConnectionInfo); - } - - private void ExpandPreviouslyOpenedFolders() - { - var containerList = ConnectionTreeModel.GetRecursiveChildList(olvConnections.GetRootConnectionNode()).OfType(); - var previouslyExpandedNodes = containerList.Where(container => container.IsExpanded); - olvConnections.ExpandedObjects = previouslyExpandedNodes; - olvConnections.InvokeRebuildAll(true); - } - - private void OpenConnectionsFromLastSession() - { - if (!Settings.Default.OpenConsFromLastSession || Settings.Default.NoReconnect) return; - var connectionInfoList = olvConnections.GetRootConnectionNode().GetRecursiveChildList().Where(node => !(node is ContainerInfo)); - var previouslyOpenedConnections = connectionInfoList.Where(item => item.PleaseConnect); - foreach (var connectionInfo in previouslyOpenedConnections) - { - ConnectionInitiator.OpenConnection(connectionInfo); - } - } - #endregion - - #region ConnectionTree - public void DuplicateSelectedNode() => olvConnections.DuplicateSelectedNode(); - - public void RenameSelectedNode() => olvConnections.RenameSelectedNode(); - - public void DeleteSelectedNode() - { - if (SelectedNode is RootNodeInfo || SelectedNode is PuttySessionInfo) return; - if (!UserConfirmsDeletion()) return; - ConnectionTreeModel.DeleteNode(SelectedNode); - Runtime.SaveConnectionsAsync(); - } - - private bool UserConfirmsDeletion() - { - var selectedNodeAsContainer = SelectedNode as ContainerInfo; - if (selectedNodeAsContainer != null) - return selectedNodeAsContainer.HasChildren() - ? UserConfirmsNonEmptyFolderDeletion() - : UserConfirmsEmptyFolderDeletion(); - return UserConfirmsConnectionDeletion(); - } - - private bool UserConfirmsEmptyFolderDeletion() - { - var messagePrompt = string.Format(Language.strConfirmDeleteNodeFolder, SelectedNode.Name); - return PromptUser(messagePrompt); - } - - private bool UserConfirmsNonEmptyFolderDeletion() - { - var messagePrompt = string.Format(Language.strConfirmDeleteNodeFolderNotEmpty, SelectedNode.Name); - return PromptUser(messagePrompt); - } - - private bool UserConfirmsConnectionDeletion() - { - var messagePrompt = string.Format(Language.strConfirmDeleteNodeConnection, SelectedNode.Name); - return PromptUser(messagePrompt); - } - - private static bool PromptUser(string promptMessage) - { - var msgBoxResponse = MessageBox.Show(promptMessage, Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question); - return (msgBoxResponse == DialogResult.Yes); - } - - private void HandleCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) - { - var senderAsContainerInfo = sender as ContainerInfo; - // ReSharper disable once SwitchStatementMissingSomeCases - switch (args?.Action) - { - case NotifyCollectionChangedAction.Add: - var childList = senderAsContainerInfo?.Children; - ConnectionInfo otherChild = null; - if (childList?.Count > 1) - otherChild = childList.First(child => !args.NewItems.Contains(child)); - RefreshTreeObject(otherChild ?? senderAsContainerInfo); - break; - case NotifyCollectionChangedAction.Remove: - RefreshTreeObjects(args.OldItems); - break; - case NotifyCollectionChangedAction.Move: - RefreshTreeObjects(args.OldItems); - break; - case NotifyCollectionChangedAction.Reset: - RefreshTreeObject(senderAsContainerInfo); - break; - case NotifyCollectionChangedAction.Replace: - break; - case null: - break; - } - } - - private void RefreshTreeObject(ConnectionInfo modelObject) - { - olvConnections.RefreshObject(modelObject); - } - - private void RefreshTreeObjects(IList modelObjects) - { - olvConnections.RefreshObjects(modelObjects); - } - - public void AddConnection() - { - try - { - AddNode(new ConnectionInfo()); - } - catch (Exception ex) - { - Runtime.MessageCollector.AddExceptionStackTrace("UI.Window.Tree.AddConnection() failed.", ex); - } - } - - public void AddFolder() - { - try - { - AddNode(new ContainerInfo()); - } - catch (Exception ex) - { - Runtime.MessageCollector.AddExceptionStackTrace(Language.strErrorAddFolderFailed, ex); - } - } - - private void AddNode(ConnectionInfo newNode) - { - if (SelectedNode == null) return; - DefaultConnectionInfo.Instance.SaveTo(newNode); - DefaultConnectionInheritance.Instance.SaveTo(newNode.Inheritance); - var selectedContainer = SelectedNode as ContainerInfo; - var parent = selectedContainer ?? SelectedNode?.Parent; - newNode.SetParent(parent); - olvConnections.Expand(parent); - olvConnections.SelectObject(newNode); - olvConnections.EnsureModelVisible(newNode); - } - - private void DisconnectConnection(ConnectionInfo connectionInfo) - { - try - { - if (connectionInfo == null) return; - var nodeAsContainer = connectionInfo as ContainerInfo; - if (nodeAsContainer != null) - { - foreach (var child in nodeAsContainer.Children) - { - for (var i = 0; i <= child.OpenConnections.Count - 1; i++) - { - child.OpenConnections[i].Disconnect(); - } - } - } - else - { - for (var i = 0; i <= connectionInfo.OpenConnections.Count - 1; i++) - { - connectionInfo.OpenConnections[i].Disconnect(); - } - } - } - catch (Exception ex) - { - Runtime.MessageCollector.AddExceptionStackTrace("DisconnectConnection (UI.Window.ConnectionTreeWindow) failed", ex); - } - } - - private void SshTransferFile() - { - try - { - Windows.Show(WindowType.SSHTransfer); - Windows.SshtransferForm.Hostname = SelectedNode.Hostname; - Windows.SshtransferForm.Username = SelectedNode.Username; - Windows.SshtransferForm.Password = SelectedNode.Password; - Windows.SshtransferForm.Port = Convert.ToString(SelectedNode.Port); - } - catch (Exception ex) - { - Runtime.MessageCollector.AddExceptionStackTrace("SSHTransferFile (UI.Window.ConnectionTreeWindow) failed", ex); - } - } - - private void StartExternalApp(ExternalTool externalTool) - { - try - { - if (SelectedNode.GetTreeNodeType() == TreeNodeType.Connection | SelectedNode.GetTreeNodeType() == TreeNodeType.PuttySession) - externalTool.Start(SelectedNode); - } - catch (Exception ex) - { - Runtime.MessageCollector.AddExceptionStackTrace("cMenTreeToolsExternalAppsEntry_Click failed (UI.Window.ConnectionTreeWindow)", ex); - } - } - - private void ExpandRootConnectionNode() - { - var rootConnectionNode = olvConnections.GetRootConnectionNode(); - olvConnections.InvokeExpand(rootConnectionNode); - } - - public void EnsureRootNodeVisible() - { - olvConnections.EnsureModelVisible(olvConnections.GetRootConnectionNode()); - } - - private void tvConnections_AfterSelect(object sender, EventArgs e) - { - try - { - Windows.ConfigForm.SelectedTreeNode = SelectedNode; - } - catch (Exception ex) - { - Runtime.MessageCollector.AddExceptionStackTrace("tvConnections_AfterSelect (UI.Window.ConnectionTreeWindow) failed", ex); - } - } - - private void tvConnections_NodeMouseSingleClick(object sender, CellClickEventArgs e) - { - try - { - if (e.ClickCount > 1) return; - var clickedNode = e.Model as ConnectionInfo; - - if (clickedNode == null) return; - if (clickedNode.GetTreeNodeType() != TreeNodeType.Connection && clickedNode.GetTreeNodeType() != TreeNodeType.PuttySession) return; - if (Settings.Default.SingleClickOnConnectionOpensIt) - ConnectionInitiator.OpenConnection(SelectedNode); - - if (Settings.Default.SingleClickSwitchesToOpenConnection) - ConnectionInitiator.SwitchToOpenConnection(SelectedNode); - } - catch (Exception ex) - { - Runtime.MessageCollector.AddExceptionStackTrace("tvConnections_NodeMouseClick (UI.Window.ConnectionTreeWindow) failed", ex); - } - } - - private void tvConnections_NodeMouseDoubleClick(object sender, CellClickEventArgs e) - { - if (e.ClickCount < 2) return; - var clickedNodeAsContainer = e.Model as ContainerInfo; - if (clickedNodeAsContainer != null) - { - olvConnections.ToggleExpansion(clickedNodeAsContainer); - } - - var clickedNode = e.Model as ConnectionInfo; - if (clickedNode?.GetTreeNodeType() == TreeNodeType.Connection | - clickedNode?.GetTreeNodeType() == TreeNodeType.PuttySession) - { - ConnectionInitiator.OpenConnection(SelectedNode); - } - } - - private void tvConnections_CellToolTipShowing(object sender, ToolTipShowingEventArgs e) - { - try - { - var nodeProducingTooltip = (ConnectionInfo)e.Model; - e.Text = nodeProducingTooltip.Description; - } - catch (Exception ex) - { - Runtime.MessageCollector.AddExceptionStackTrace("tvConnections_MouseMove (UI.Window.ConnectionTreeWindow) failed", ex); - } - } #endregion #region Top Menu @@ -562,11 +152,11 @@ namespace mRemoteNG.UI.Window else ConnectionInitiator.OpenConnection(SelectedNode, ConnectionInfo.Force.OverridePanel | ConnectionInfo.Force.DoNotJump); }; - _contextMenu.DisconnectClicked += (sender, args) => DisconnectConnection(SelectedNode); - _contextMenu.TransferFileClicked += (sender, args) => SshTransferFile(); - _contextMenu.DuplicateClicked += (sender, args) => DuplicateSelectedNode(); - _contextMenu.RenameClicked += (sender, args) => RenameSelectedNode(); - _contextMenu.DeleteClicked += (sender, args) => DeleteSelectedNode(); + _contextMenu.DisconnectClicked += (sender, args) => olvConnections.DisconnectConnection(SelectedNode); + _contextMenu.TransferFileClicked += (sender, args) => olvConnections.SshTransferFile(); + _contextMenu.DuplicateClicked += (sender, args) => olvConnections.DuplicateSelectedNode(); + _contextMenu.RenameClicked += (sender, args) => olvConnections.RenameSelectedNode(); + _contextMenu.DeleteClicked += (sender, args) => olvConnections.DeleteSelectedNode(); _contextMenu.ImportFileClicked += (sender, args) => { var selectedNodeAsContainer = SelectedNode as ContainerInfo ?? SelectedNode.Parent; @@ -581,18 +171,18 @@ namespace mRemoteNG.UI.Window _contextMenu.SortDescendingClicked += (sender, args) => SortNodesRecursive(SelectedNode, ListSortDirection.Descending); _contextMenu.MoveUpClicked += cMenTreeMoveUp_Click; _contextMenu.MoveDownClicked += cMenTreeMoveDown_Click; - _contextMenu.ExternalToolClicked += (sender, args) => StartExternalApp((ExternalTool)((ToolStripMenuItem)sender).Tag); + _contextMenu.ExternalToolClicked += (sender, args) => olvConnections.StartExternalApp((ExternalTool)((ToolStripMenuItem)sender).Tag); } private void cMenTreeAddConnection_Click(object sender, EventArgs e) { - AddConnection(); + olvConnections.AddConnection(); Runtime.SaveConnectionsAsync(); } private void cMenTreeAddFolder_Click(object sender, EventArgs e) { - AddFolder(); + olvConnections.AddFolder(); Runtime.SaveConnectionsAsync(); } @@ -632,7 +222,7 @@ namespace mRemoteNG.UI.Window try { _contextMenu.EnableShortcutKeys(); - ConnectionTreeModel.RenameNode(SelectedNode, e.Label); + ConnectionTree.ConnectionTreeModel.RenameNode(SelectedNode, e.Label); Windows.ConfigForm.SelectedTreeNode = SelectedNode; Runtime.SaveConnectionsAsync(); } @@ -669,13 +259,13 @@ namespace mRemoteNG.UI.Window } else if (e.KeyCode == Keys.Up) { - var match = _nodeSearcher.PreviousMatch(); + var match = olvConnections.NodeSearcher.PreviousMatch(); JumpToNode(match); e.Handled = true; } else if (e.KeyCode == Keys.Down) { - var match = _nodeSearcher.NextMatch(); + var match = olvConnections.NodeSearcher.NextMatch(); JumpToNode(match); e.Handled = true; } @@ -693,8 +283,8 @@ namespace mRemoteNG.UI.Window private void txtSearch_TextChanged(object sender, EventArgs e) { if (txtSearch.Text == "") return; - _nodeSearcher?.SearchByName(txtSearch.Text); - JumpToNode(_nodeSearcher?.CurrentMatch); + olvConnections.NodeSearcher?.SearchByName(txtSearch.Text); + JumpToNode(olvConnections.NodeSearcher?.CurrentMatch); } private void JumpToNode(ConnectionInfo connectionInfo) diff --git a/mRemoteV1/UI/Window/ConnectionTreeWindow.resx b/mRemoteV1/UI/Window/ConnectionTreeWindow.resx index dfeabba7..584bd685 100644 --- a/mRemoteV1/UI/Window/ConnectionTreeWindow.resx +++ b/mRemoteV1/UI/Window/ConnectionTreeWindow.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - 17, 17 - 119, 19