diff --git a/src/DynamoCore/Configuration/PreferenceSettings.cs b/src/DynamoCore/Configuration/PreferenceSettings.cs
index 59bb9be117d..0045e373c7e 100644
--- a/src/DynamoCore/Configuration/PreferenceSettings.cs
+++ b/src/DynamoCore/Configuration/PreferenceSettings.cs
@@ -333,6 +333,11 @@ public string PythonTemplateFilePath
///
public bool ShowTabsAndSpacesInScriptEditor { get; set; }
+ ///
+ /// This defines if user wants to see the enabled node Auto Complete feature for port interaction.
+ ///
+ public bool EnableNodeAutoComplete { get; set; }
+
///
/// Engine used by default for new Python script and string nodes. If not empty, this takes precedence over any system settings.
///
@@ -408,6 +413,7 @@ public PreferenceSettings()
PythonTemplateFilePath = "";
IsIronPythonDialogDisabled = false;
ShowTabsAndSpacesInScriptEditor = false;
+ EnableNodeAutoComplete = false;
DefaultPythonEngine = string.Empty;
}
diff --git a/src/DynamoCore/Graph/Nodes/PortModel.cs b/src/DynamoCore/Graph/Nodes/PortModel.cs
index 9bd3b3baaf2..8bbaa513bee 100644
--- a/src/DynamoCore/Graph/Nodes/PortModel.cs
+++ b/src/DynamoCore/Graph/Nodes/PortModel.cs
@@ -2,9 +2,12 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
+using System.Reflection;
using System.Xml;
using Dynamo.Configuration;
+using Dynamo.Engine;
using Dynamo.Graph.Connectors;
+using Dynamo.Graph.Nodes.ZeroTouch;
using Dynamo.Graph.Workspaces;
using Dynamo.Utilities;
using Newtonsoft.Json;
@@ -386,6 +389,55 @@ public override int GetHashCode()
return GUID.GetHashCode();
}
+ ///
+ /// Returns the string representation of the fully qualified typename
+ /// where possible for the port if it's an input port. This method currently
+ /// returns a valid type for only Zero Touch, Builtin and NodeModel nodes,
+ /// and returns null otherwise. The string representation of the type also
+ /// contains the rank information of the type, e.g. Point[], or var[]..[].
+ ///
+ /// input port type
+ internal string GetInputPortType()
+ {
+ if (PortType == PortType.Output) return null;
+
+ var ztNode = Owner as DSFunction;
+ if (ztNode != null)
+ {
+ var fd = ztNode.Controller.Definition;
+ string type;
+ // In the case of a node for an instance method, the first port
+ // type is the declaring class type of the method itself.
+ if (fd.Type == FunctionType.InstanceMethod)
+ {
+ if (Index > 0)
+ {
+ var param = fd.Parameters.ElementAt(Index - 1);
+ type = param.Type.ToString();
+ }
+ else
+ {
+ type = fd.ClassName;
+ }
+ }
+ else
+ {
+ var param = fd.Parameters.ElementAt(Index);
+ type = param.Type.ToString();
+ }
+ return type;
+ }
+
+ var nmNode = Owner as NodeModel;
+ if (nmNode != null)
+ {
+ var classType = nmNode.GetType();
+ var inPortAttribute = classType.GetCustomAttributes().OfType().FirstOrDefault();
+
+ return inPortAttribute?.PortTypes.ElementAt(Index);
+ }
+ return null;
+ }
}
///
diff --git a/src/DynamoCore/Search/SearchElements/NodeSearchElement.cs b/src/DynamoCore/Search/SearchElements/NodeSearchElement.cs
index a76902d9201..2cb2da77bc6 100644
--- a/src/DynamoCore/Search/SearchElements/NodeSearchElement.cs
+++ b/src/DynamoCore/Search/SearchElements/NodeSearchElement.cs
@@ -171,7 +171,7 @@ public SearchElementGroup Group
}
///
- /// Group to which Node belongs to
+ /// Assembly to which Node belongs to
///
public string Assembly
{
diff --git a/src/DynamoCoreWpf/Commands/PortCommands.cs b/src/DynamoCoreWpf/Commands/PortCommands.cs
index b3400e9112e..35383aa57ff 100644
--- a/src/DynamoCoreWpf/Commands/PortCommands.cs
+++ b/src/DynamoCoreWpf/Commands/PortCommands.cs
@@ -4,7 +4,8 @@ namespace Dynamo.ViewModels
{
public partial class PortViewModel
{
- private DelegateCommand _connectCommand;
+ private DelegateCommand connectCommand;
+ private DelegateCommand autoCompleteCommand;
private DelegateCommand portMouseEnterCommand;
private DelegateCommand portMouseLeaveCommand;
private DelegateCommand portMouseLeftButtonCommand;
@@ -15,10 +16,24 @@ public DelegateCommand ConnectCommand
{
get
{
- if(_connectCommand == null)
- _connectCommand = new DelegateCommand(Connect, CanConnect);
+ if(connectCommand == null)
+ connectCommand = new DelegateCommand(Connect, CanConnect);
- return _connectCommand;
+ return connectCommand;
+ }
+ }
+
+ ///
+ /// Command to trigger Node Auto Complete from node port interaction
+ ///
+ public DelegateCommand NodeAutoCompleteCommand
+ {
+ get
+ {
+ if (autoCompleteCommand == null)
+ autoCompleteCommand = new DelegateCommand(AutoComplete, CanAutoComplete);
+
+ return autoCompleteCommand;
}
}
diff --git a/src/DynamoCoreWpf/Commands/WorkspaceCommands.cs b/src/DynamoCoreWpf/Commands/WorkspaceCommands.cs
index a18414c5c65..364ba07953a 100644
--- a/src/DynamoCoreWpf/Commands/WorkspaceCommands.cs
+++ b/src/DynamoCoreWpf/Commands/WorkspaceCommands.cs
@@ -12,23 +12,20 @@ public partial class WorkspaceViewModel
{
#region Private Delegate Command Data Members
- private DelegateCommand _hideCommand;
- private DelegateCommand _setCurrentOffsetCommand;
- private DelegateCommand _nodeFromSelectionCommand;
- private DelegateCommand _setZoomCommand;
- private DelegateCommand _resetFitViewToggleCommand;
- private DelegateCommand _findByIdCommand;
- private DelegateCommand _alignSelectedCommand;
- private DelegateCommand _setArgumentLacingCommand;
- private DelegateCommand _findNodesFromSelectionCommand;
- private DelegateCommand _selectAllCommand;
- private DelegateCommand _graphAutoLayoutCommand;
- private DelegateCommand _pauseVisualizationManagerUpdateCommand;
- private DelegateCommand _unpauseVisualizationManagerUpdateCommand;
- private DelegateCommand _showHideAllGeometryPreviewCommand;
- private DelegateCommand _showInCanvasSearchCommand;
- private DelegateCommand _pasteCommand;
- private DelegateCommand _computeRunStateCommand;
+ private DelegateCommand hideCommand;
+ private DelegateCommand setCurrentOffsetCommand;
+ private DelegateCommand nodeFromSelectionCommand;
+ private DelegateCommand setZoomCommand;
+ private DelegateCommand resetFitViewToggleCommand;
+ private DelegateCommand findByIdCommand;
+ private DelegateCommand alignSelectedCommand;
+ private DelegateCommand setArgumentLacingCommand;
+ private DelegateCommand findNodesFromSelectionCommand;
+ private DelegateCommand selectAllCommand;
+ private DelegateCommand graphAutoLayoutCommand;
+ private DelegateCommand showHideAllGeometryPreviewCommand;
+ private DelegateCommand showInCanvasSearchCommand;
+ private DelegateCommand pasteCommand;
#endregion
@@ -43,7 +40,7 @@ public DelegateCommand CopyCommand
[JsonIgnore]
public DelegateCommand PasteCommand
{
- get { return _pasteCommand ?? (_pasteCommand = new DelegateCommand(Paste, DynamoViewModel.CanPaste)); }
+ get { return pasteCommand ?? (pasteCommand = new DelegateCommand(Paste, DynamoViewModel.CanPaste)); }
}
[JsonIgnore]
@@ -51,9 +48,9 @@ public DelegateCommand SelectAllCommand
{
get
{
- if(_selectAllCommand == null)
- _selectAllCommand = new DelegateCommand(SelectAll, CanSelectAll);
- return _selectAllCommand;
+ if(selectAllCommand == null)
+ selectAllCommand = new DelegateCommand(SelectAll, CanSelectAll);
+ return selectAllCommand;
}
}
@@ -61,8 +58,8 @@ public DelegateCommand SelectAllCommand
public DelegateCommand GraphAutoLayoutCommand
{
get {
- return _graphAutoLayoutCommand
- ?? (_graphAutoLayoutCommand =
+ return graphAutoLayoutCommand
+ ?? (graphAutoLayoutCommand =
new DelegateCommand(DoGraphAutoLayout, CanDoGraphAutoLayout));
}
}
@@ -86,10 +83,10 @@ public DelegateCommand HideCommand
{
get
{
- if(_hideCommand == null)
- _hideCommand = new DelegateCommand(Hide, CanHide);
+ if(hideCommand == null)
+ hideCommand = new DelegateCommand(Hide, CanHide);
- return _hideCommand;
+ return hideCommand;
}
}
@@ -98,10 +95,10 @@ public DelegateCommand SetCurrentOffsetCommand
{
get
{
- if(_setCurrentOffsetCommand == null)
- _setCurrentOffsetCommand = new DelegateCommand(SetCurrentOffset, CanSetCurrentOffset);
+ if(setCurrentOffsetCommand == null)
+ setCurrentOffsetCommand = new DelegateCommand(SetCurrentOffset, CanSetCurrentOffset);
- return _setCurrentOffsetCommand;
+ return setCurrentOffsetCommand;
}
}
@@ -110,10 +107,10 @@ public DelegateCommand NodeFromSelectionCommand
{
get
{
- if(_nodeFromSelectionCommand == null)
- _nodeFromSelectionCommand = new DelegateCommand(CreateNodeFromSelection, CanCreateNodeFromSelection);
+ if(nodeFromSelectionCommand == null)
+ nodeFromSelectionCommand = new DelegateCommand(CreateNodeFromSelection, CanCreateNodeFromSelection);
- return _nodeFromSelectionCommand;
+ return nodeFromSelectionCommand;
}
}
@@ -122,9 +119,9 @@ public DelegateCommand SetZoomCommand
{
get
{
- if(_setZoomCommand == null)
- _setZoomCommand = new DelegateCommand(SetZoom, CanSetZoom);
- return _setZoomCommand;
+ if(setZoomCommand == null)
+ setZoomCommand = new DelegateCommand(SetZoom, CanSetZoom);
+ return setZoomCommand;
}
}
@@ -133,9 +130,9 @@ public DelegateCommand ResetFitViewToggleCommand
{
get
{
- if (_resetFitViewToggleCommand == null)
- _resetFitViewToggleCommand = new DelegateCommand(ResetFitViewToggle, CanResetFitViewToggle);
- return _resetFitViewToggleCommand;
+ if (resetFitViewToggleCommand == null)
+ resetFitViewToggleCommand = new DelegateCommand(ResetFitViewToggle, CanResetFitViewToggle);
+ return resetFitViewToggleCommand;
}
}
@@ -144,10 +141,10 @@ public DelegateCommand FindByIdCommand
{
get
{
- if(_findByIdCommand == null)
- _findByIdCommand = new DelegateCommand(FindById, CanFindById);
+ if(findByIdCommand == null)
+ findByIdCommand = new DelegateCommand(FindById, CanFindById);
- return _findByIdCommand;
+ return findByIdCommand;
}
}
@@ -156,10 +153,10 @@ public DelegateCommand AlignSelectedCommand
{
get
{
- if(_alignSelectedCommand == null)
- _alignSelectedCommand = new DelegateCommand(AlignSelected, CanAlignSelected);
+ if(alignSelectedCommand == null)
+ alignSelectedCommand = new DelegateCommand(AlignSelected, CanAlignSelected);
- return _alignSelectedCommand;
+ return alignSelectedCommand;
}
}
@@ -168,13 +165,13 @@ public DelegateCommand SetArgumentLacingCommand
{
get
{
- if (_setArgumentLacingCommand == null)
+ if (setArgumentLacingCommand == null)
{
- _setArgumentLacingCommand = new DelegateCommand(
+ setArgumentLacingCommand = new DelegateCommand(
SetArgumentLacing, p => HasSelection);
}
- return _setArgumentLacingCommand;
+ return setArgumentLacingCommand;
}
}
@@ -183,10 +180,10 @@ public DelegateCommand FindNodesFromSelectionCommand
{
get
{
- if(_findNodesFromSelectionCommand == null)
- _findNodesFromSelectionCommand = new DelegateCommand(FindNodesFromSelection, CanFindNodesFromSelection);
+ if(findNodesFromSelectionCommand == null)
+ findNodesFromSelectionCommand = new DelegateCommand(FindNodesFromSelection, CanFindNodesFromSelection);
- return _findNodesFromSelectionCommand;
+ return findNodesFromSelectionCommand;
}
}
@@ -195,13 +192,13 @@ public DelegateCommand ShowHideAllGeometryPreviewCommand
{
get
{
- if (_showHideAllGeometryPreviewCommand == null)
+ if (showHideAllGeometryPreviewCommand == null)
{
- _showHideAllGeometryPreviewCommand = new DelegateCommand(
+ showHideAllGeometryPreviewCommand = new DelegateCommand(
ShowHideAllGeometryPreview);
}
- return _showHideAllGeometryPreviewCommand;
+ return showHideAllGeometryPreviewCommand;
}
}
@@ -211,13 +208,12 @@ public DelegateCommand ShowInCanvasSearchCommand
{
get
{
- if (_showInCanvasSearchCommand == null)
- _showInCanvasSearchCommand = new DelegateCommand(OnRequestShowInCanvasSearch);
+ if (showInCanvasSearchCommand == null)
+ showInCanvasSearchCommand = new DelegateCommand(OnRequestShowInCanvasSearch);
- return _showInCanvasSearchCommand;
+ return showInCanvasSearchCommand;
}
}
-
#endregion
#region Properties for Command Data Binding
diff --git a/src/DynamoCoreWpf/Controls/NodeAutoCompleteSearchControl.xaml b/src/DynamoCoreWpf/Controls/NodeAutoCompleteSearchControl.xaml
new file mode 100644
index 00000000000..7efa7fc889b
--- /dev/null
+++ b/src/DynamoCoreWpf/Controls/NodeAutoCompleteSearchControl.xaml
@@ -0,0 +1,152 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DynamoCoreWpf/Controls/NodeAutoCompleteSearchControl.xaml.cs b/src/DynamoCoreWpf/Controls/NodeAutoCompleteSearchControl.xaml.cs
new file mode 100644
index 00000000000..f01ec130bd1
--- /dev/null
+++ b/src/DynamoCoreWpf/Controls/NodeAutoCompleteSearchControl.xaml.cs
@@ -0,0 +1,258 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Data;
+using System.Windows.Input;
+using System.Windows.Threading;
+using Dynamo.Utilities;
+using Dynamo.ViewModels;
+using Dynamo.Wpf.ViewModels;
+
+namespace Dynamo.UI.Controls
+{
+ ///
+ /// Interaction logic for AutoCompleteSearchControl.xaml
+ /// Notice this control shares a lot of logic with InCanvasSearchControl for now
+ /// But they will diverge eventually because of UI improvements to auto complete.
+ ///
+ public partial class NodeAutoCompleteSearchControl
+ {
+ ListBoxItem HighlightedItem;
+
+ internal event Action RequestShowNodeAutoCompleteSearch;
+
+ public NodeAutoCompleteSearchViewModel ViewModel
+ {
+ get { return DataContext as NodeAutoCompleteSearchViewModel; }
+ }
+
+ public NodeAutoCompleteSearchControl()
+ {
+ InitializeComponent();
+ if (Application.Current != null)
+ {
+ Application.Current.Deactivated += currentApplicationDeactivated;
+ }
+ Unloaded += NodeAutoCompleteSearchControl_Unloaded;
+
+ }
+
+ private void NodeAutoCompleteSearchControl_Unloaded(object sender, RoutedEventArgs e)
+ {
+ if (Application.Current != null)
+ {
+ Application.Current.Deactivated -= currentApplicationDeactivated;
+ }
+ }
+
+ private void currentApplicationDeactivated(object sender, EventArgs e)
+ {
+ OnRequestShowNodeAutoCompleteSearch(ShowHideFlags.Hide);
+ }
+
+ private void OnRequestShowNodeAutoCompleteSearch(ShowHideFlags flags)
+ {
+ if (RequestShowNodeAutoCompleteSearch != null)
+ {
+ RequestShowNodeAutoCompleteSearch(flags);
+ }
+ }
+
+ private void OnSearchTextBoxTextChanged(object sender, TextChangedEventArgs e)
+ {
+ BindingExpression binding = ((TextBox)sender).GetBindingExpression(TextBox.TextProperty);
+ if (binding != null)
+ binding.UpdateSource();
+
+ if (ViewModel != null)
+ ViewModel.SearchCommand.Execute(null);
+ }
+
+ private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
+ {
+ var listBoxItem = sender as ListBoxItem;
+ if (listBoxItem == null || e.OriginalSource is Thumb) return;
+
+ ExecuteSearchElement(listBoxItem);
+ OnRequestShowNodeAutoCompleteSearch(ShowHideFlags.Hide);
+ e.Handled = true;
+ }
+
+ private void ExecuteSearchElement(ListBoxItem listBoxItem)
+ {
+ var searchElement = listBoxItem.DataContext as NodeSearchElementViewModel;
+ if (searchElement != null)
+ {
+ searchElement.Position = ViewModel.InCanvasSearchPosition;
+ PortViewModel port = ViewModel.PortViewModel;
+ searchElement.CreateAndConnectCommand.Execute(port.PortModel);
+ }
+ }
+
+ private void OnMouseEnter(object sender, MouseEventArgs e)
+ {
+ FrameworkElement fromSender = sender as FrameworkElement;
+ if (fromSender == null) return;
+
+ toolTipPopup.DataContext = fromSender.DataContext;
+ toolTipPopup.IsOpen = true;
+ }
+
+ private void OnMouseLeave(object sender, MouseEventArgs e)
+ {
+ toolTipPopup.DataContext = null;
+ toolTipPopup.IsOpen = false;
+ }
+
+ private void OnNodeAutoCompleteSearchControlVisibilityChanged(object sender, DependencyPropertyChangedEventArgs e)
+ {
+ // If visibility is false, then stop processing it.
+ if (!(bool)e.NewValue)
+ return;
+
+ // When launching this control, always start with clear search term.
+ SearchTextBox.Clear();
+
+ // Visibility of textbox changed, but text box has not been initialized(rendered) yet.
+ // Call asynchronously focus, when textbox will be ready.
+ Dispatcher.BeginInvoke(new Action(() =>
+ {
+ SearchTextBox.Focus();
+ //ViewModel.InitializeDefaultAutoCompleteCandidates();
+ ViewModel.PopulateAutoCompleteCandidates();
+ }), DispatcherPriority.Loaded);
+ }
+
+ private void OnMembersListBoxUpdated(object sender, DataTransferEventArgs e)
+ {
+ var membersListBox = sender as ListBox;
+ // As soon as listbox renders, select first member.
+ membersListBox.ItemContainerGenerator.StatusChanged += OnMembersListBoxIcgStatusChanged;
+ }
+
+ private void OnMembersListBoxIcgStatusChanged(object sender, EventArgs e)
+ {
+ if (MembersListBox.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
+ {
+ MembersListBox.ItemContainerGenerator.StatusChanged -= OnMembersListBoxIcgStatusChanged;
+ Dispatcher.BeginInvoke(new Action(() =>
+ {
+ var scrollViewer = MembersListBox.ChildOfType();
+ scrollViewer.ScrollToTop();
+
+ UpdateHighlightedItem(GetListItemByIndex(MembersListBox, 0));
+ }),
+ DispatcherPriority.Loaded);
+ }
+ }
+
+ private void UpdateHighlightedItem(ListBoxItem newItem)
+ {
+ if (HighlightedItem == newItem)
+ return;
+
+ // Unselect old value.
+ if (HighlightedItem != null)
+ HighlightedItem.IsSelected = false;
+
+ HighlightedItem = newItem;
+
+ // Select new value.
+ if (HighlightedItem != null)
+ {
+ HighlightedItem.IsSelected = true;
+ HighlightedItem.BringIntoView();
+ }
+ }
+
+ private ListBoxItem GetListItemByIndex(ListBox parent, int index)
+ {
+ if (parent.Equals(null)) return null;
+
+ var generator = parent.ItemContainerGenerator;
+ if ((index >= 0) && (index < parent.Items.Count))
+ return generator.ContainerFromIndex(index) as ListBoxItem;
+
+ return null;
+ }
+
+ private void OnInCanvasSearchKeyDown(object sender, KeyEventArgs e)
+ {
+ var key = e.Key;
+
+ int index;
+ var members = MembersListBox.Items.Cast();
+ NodeSearchElementViewModel highlightedMember = null;
+ if (HighlightedItem != null)
+ highlightedMember = HighlightedItem.DataContext as NodeSearchElementViewModel;
+
+ switch (key)
+ {
+ case Key.Escape:
+ OnRequestShowNodeAutoCompleteSearch(ShowHideFlags.Hide);
+ break;
+ case Key.Enter:
+ if (HighlightedItem != null && ViewModel.CurrentMode != SearchViewModel.ViewMode.LibraryView)
+ {
+ ExecuteSearchElement(HighlightedItem);
+ OnRequestShowNodeAutoCompleteSearch(ShowHideFlags.Hide);
+ }
+ break;
+ case Key.Up:
+ index = MoveToNextMember(false, members, highlightedMember);
+ UpdateHighlightedItem(GetListItemByIndex(MembersListBox, index));
+ break;
+ case Key.Down:
+ index = MoveToNextMember(true, members, highlightedMember);
+ UpdateHighlightedItem(GetListItemByIndex(MembersListBox, index));
+ break;
+ }
+ }
+
+ internal int MoveToNextMember(bool moveForward,
+ IEnumerable members, NodeSearchElementViewModel selectedMember)
+ {
+ int selectedMemberIndex = -1;
+ for (int i = 0; i < members.Count(); i++)
+ {
+ var member = members.ElementAt(i);
+ if (member.Equals(selectedMember))
+ {
+ selectedMemberIndex = i;
+ break;
+ }
+ }
+
+ int nextselectedMemberIndex = selectedMemberIndex;
+ if (moveForward)
+ nextselectedMemberIndex++;
+ else
+ nextselectedMemberIndex--;
+
+ if (nextselectedMemberIndex < 0 || (nextselectedMemberIndex >= members.Count()))
+ return selectedMemberIndex;
+
+ return nextselectedMemberIndex;
+ }
+
+ private void OnMembersListBoxMouseWheel(object sender, MouseWheelEventArgs e)
+ {
+ var listBox = sender as FrameworkElement;
+ if (listBox == null)
+ return;
+
+ var scrollViewer = listBox.ChildOfType();
+ if (scrollViewer == null)
+ return;
+
+ // Make delta less to achieve smooth scrolling and not jump over other elements.
+ var delta = e.Delta / 100;
+ scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - delta);
+ // do not propagate to child items with scrollable content
+ e.Handled = true;
+ }
+ }
+}
diff --git a/src/DynamoCoreWpf/DynamoCoreWpf.csproj b/src/DynamoCoreWpf/DynamoCoreWpf.csproj
index ff706ec0a90..57f42d23995 100644
--- a/src/DynamoCoreWpf/DynamoCoreWpf.csproj
+++ b/src/DynamoCoreWpf/DynamoCoreWpf.csproj
@@ -121,6 +121,9 @@
+
+ NodeAutoCompleteSearchControl.xaml
+
InCanvasSearchControl.xaml
@@ -294,6 +297,7 @@
+
@@ -420,6 +424,10 @@
+
+ Designer
+ MSBuild:Compile
+
MSBuild:Compile
Designer
diff --git a/src/DynamoCoreWpf/Properties/Resources.Designer.cs b/src/DynamoCoreWpf/Properties/Resources.Designer.cs
index 330ed701366..22e288d9e5e 100644
--- a/src/DynamoCoreWpf/Properties/Resources.Designer.cs
+++ b/src/DynamoCoreWpf/Properties/Resources.Designer.cs
@@ -1538,6 +1538,15 @@ public static string DynamoViewSamplesMenuShowInFolder {
}
}
+ ///
+ /// Looks up a localized string similar to Enable Node Auto Complete.
+ ///
+ public static string DynamoViewSettingEnableNodeAutoComplete {
+ get {
+ return ResourceManager.GetString("DynamoViewSettingEnableNodeAutoComplete", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Enable T-Spline nodes (requires relaunch of Dynamo).
///
diff --git a/src/DynamoCoreWpf/Properties/Resources.en-US.resx b/src/DynamoCoreWpf/Properties/Resources.en-US.resx
index 0530b2ce219..2406547ce48 100644
--- a/src/DynamoCoreWpf/Properties/Resources.en-US.resx
+++ b/src/DynamoCoreWpf/Properties/Resources.en-US.resx
@@ -2273,4 +2273,8 @@ Uninstall the following packages: {0}?
Use System Default
+
+ Enable Node Auto Complete
+ Setting menu | Experimental | Enable Node Auto Complete
+
\ No newline at end of file
diff --git a/src/DynamoCoreWpf/Properties/Resources.resx b/src/DynamoCoreWpf/Properties/Resources.resx
index 94847517740..fd24f063dbe 100644
--- a/src/DynamoCoreWpf/Properties/Resources.resx
+++ b/src/DynamoCoreWpf/Properties/Resources.resx
@@ -2275,4 +2275,8 @@ Uninstall the following packages: {0}?
Use System Default
Settings Menu | Default Python Engine (first option, rest are purposely not translated)
+
+ Enable Node Auto Complete
+ Setting menu | Experimental | Enable Node Auto Complete
+
\ No newline at end of file
diff --git a/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs
index ec42e3b1677..248e23a310e 100644
--- a/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs
+++ b/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs
@@ -310,6 +310,21 @@ public bool EnableTSpline
}
}
+ ///
+ /// Indicates whether to enabled node Auto Complete feature for port interaction.
+ ///
+ public bool EnableNodeAutoComplete
+ {
+ get
+ {
+ return PreferenceSettings.EnableNodeAutoComplete;
+ }
+ set
+ {
+ PreferenceSettings.EnableNodeAutoComplete = value;
+ }
+ }
+
public int LibraryWidth
{
get
diff --git a/src/DynamoCoreWpf/ViewModels/Core/PortViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/PortViewModel.cs
index 5a4aa72cfb3..64e8a90380d 100644
--- a/src/DynamoCoreWpf/ViewModels/Core/PortViewModel.cs
+++ b/src/DynamoCoreWpf/ViewModels/Core/PortViewModel.cs
@@ -364,6 +364,22 @@ private bool CanConnect(object parameter)
return true;
}
+ // Handler to invoke node Auto Complete
+ private void AutoComplete(object parameter)
+ {
+ DynamoViewModel dynamoViewModel = _node.DynamoViewModel;
+ var svm = dynamoViewModel.CurrentSpaceViewModel.NodeAutoCompleteSearchViewModel as NodeAutoCompleteSearchViewModel;
+ svm.PortViewModel = parameter as PortViewModel;
+ dynamoViewModel.CurrentSpaceViewModel.OnRequestNodeAutoCompleteSearch(ShowHideFlags.Show);
+ }
+
+ private bool CanAutoComplete(object parameter)
+ {
+ DynamoViewModel dynamoViewModel = _node.DynamoViewModel;
+ // If the feature is enabled from Dynamo experiment setting and if user interaction is on input port.
+ return dynamoViewModel.EnableNodeAutoComplete && this.PortType == PortType.Input;
+ }
+
///
/// Handles the Mouse enter event on the port
///
@@ -371,7 +387,9 @@ private bool CanConnect(object parameter)
private void OnRectangleMouseEnter(object parameter)
{
if (MouseEnter != null)
+ {
MouseEnter(parameter, null);
+ }
}
///
@@ -411,7 +429,5 @@ private void OnMouseLeftUseLevel(object parameter)
{
ShowUseLevelMenu = false;
}
-
-
}
}
diff --git a/src/DynamoCoreWpf/ViewModels/Core/StateMachine.cs b/src/DynamoCoreWpf/ViewModels/Core/StateMachine.cs
index 62250df0492..48c4ad10b7d 100644
--- a/src/DynamoCoreWpf/ViewModels/Core/StateMachine.cs
+++ b/src/DynamoCoreWpf/ViewModels/Core/StateMachine.cs
@@ -117,6 +117,11 @@ internal void CancelActiveState()
stateMachine.CancelActiveState();
}
+ internal DateTime GetLastStateTimestamp()
+ {
+ return stateMachine.Timestamp;
+ }
+
internal void BeginDragSelection(Point2D mouseCursor)
{
// This represents the first mouse-move event after the mouse-down
@@ -439,6 +444,11 @@ private enum State
#endregion
#region Public Class Properties
+ ///
+ /// Optionally record the last time a particular state is updated.
+ /// Currently only used for Node AutoComplete feature.
+ ///
+ internal DateTime Timestamp { get; set; }
internal bool IsInIdleState
{
@@ -802,6 +812,17 @@ internal bool HandlePortClicked(PortViewModel portViewModel)
if (this.currentState != State.Connection) // Not in a connection attempt...
{
+ if (Keyboard.Modifiers == ModifierKeys.Alt &&
+ portViewModel.NodeAutoCompleteCommand.CanExecute(portViewModel))
+ {
+ portViewModel.NodeAutoCompleteCommand.Execute(portViewModel);
+ this.currentState = State.Connection;
+ owningWorkspace.CurrentCursor = CursorLibrary.GetCursor(CursorSet.ArcSelect);
+ owningWorkspace.IsCursorForced = false;
+ Timestamp = DateTime.Now;
+ return true;
+ }
+
Guid nodeId = portModel.Owner.GUID;
int portIndex = portModel.Index;
diff --git a/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs
index c9210190190..7632ad9301f 100644
--- a/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs
+++ b/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs
@@ -158,9 +158,15 @@ public virtual void OnWorkspacePropertyEditRequested()
private void OnRequestShowInCanvasSearch(object param)
{
var flag = (ShowHideFlags)param;
+ RequestShowInCanvasSearch?.Invoke(flag);
+ }
+
+ internal event Action RequestNodeAutoCompleteSearch;
- if (RequestShowInCanvasSearch != null)
- RequestShowInCanvasSearch(flag);
+ internal void OnRequestNodeAutoCompleteSearch(object param)
+ {
+ var flag = (ShowHideFlags)param;
+ RequestNodeAutoCompleteSearch?.Invoke(flag);
}
#endregion
@@ -221,7 +227,7 @@ public DynamoPreferencesData DynamoPreferences
/// of Graph.Json.
///
[JsonProperty("Camera")]
- public CameraData Camera => DynamoViewModel.BackgroundPreviewViewModel.GetCameraInformation() ?? new CameraData();
+ public CameraData Camera => DynamoViewModel.BackgroundPreviewViewModel?.GetCameraInformation() ?? new CameraData();
///
/// ViewModel that is used in InCanvasSearch in context menu and called by Shift+DoubleClick.
@@ -229,6 +235,12 @@ public DynamoPreferencesData DynamoPreferences
[JsonIgnore]
public SearchViewModel InCanvasSearchViewModel { get; private set; }
+ ///
+ /// ViewModel that is used in NodeAutoComplete feature in context menu and called by Shift+DoubleClick.
+ ///
+ [JsonIgnore]
+ public SearchViewModel NodeAutoCompleteSearchViewModel { get; private set; }
+
///
/// Cursor Property Binding for WorkspaceView
///
@@ -456,8 +468,14 @@ public WorkspaceViewModel(WorkspaceModel model, DynamoViewModel dynamoViewModel)
foreach (AnnotationModel annotation in Model.Annotations) Model_AnnotationAdded(annotation);
foreach (ConnectorModel connector in Model.Connectors) Connectors_ConnectorAdded(connector);
- InCanvasSearchViewModel = new SearchViewModel(DynamoViewModel);
- InCanvasSearchViewModel.Visible = true;
+ InCanvasSearchViewModel = new SearchViewModel(DynamoViewModel)
+ {
+ Visible = true
+ };
+ NodeAutoCompleteSearchViewModel = new NodeAutoCompleteSearchViewModel(DynamoViewModel)
+ {
+ Visible = true
+ };
}
///
/// This event is triggered from Workspace Model. Used in instrumentation
@@ -505,6 +523,7 @@ public override void Dispose()
Connectors.Clear();
Errors.Clear();
InCanvasSearchViewModel.Dispose();
+ NodeAutoCompleteSearchViewModel.Dispose();
}
internal void ZoomInInternal()
diff --git a/src/DynamoCoreWpf/ViewModels/Search/NodeAutoCompleteSearchViewModel.cs b/src/DynamoCoreWpf/ViewModels/Search/NodeAutoCompleteSearchViewModel.cs
new file mode 100644
index 00000000000..ea135825c1a
--- /dev/null
+++ b/src/DynamoCoreWpf/ViewModels/Search/NodeAutoCompleteSearchViewModel.cs
@@ -0,0 +1,94 @@
+using System.Collections.Generic;
+using System.Linq;
+using Dynamo.Controls;
+using Dynamo.Search.SearchElements;
+using Dynamo.Wpf.ViewModels;
+
+namespace Dynamo.ViewModels
+{
+ ///
+ /// Search View Model for Node AutoComplate Search Bar
+ ///
+ public class NodeAutoCompleteSearchViewModel : SearchViewModel
+ {
+ internal PortViewModel PortViewModel { get; set; }
+
+ internal NodeAutoCompleteSearchViewModel(DynamoViewModel dynamoViewModel) : base(dynamoViewModel)
+ {
+ // Do nothing for now, but we may off load some time consuming operation here later
+ }
+
+ internal void InitializeDefaultAutoCompleteCandidates()
+ {
+ var candidates = new List();
+ // TODO: These are hard copied all time top 7 nodes placed by customers
+ // This should be only served as a temporary default case.
+ var queries = new List(){ "Code Block", "Watch", "List Flatten", "List Create", "String", "Double", "Python" };
+ foreach (var query in queries)
+ {
+ var foundNode = Search(query).ToList().FirstOrDefault();
+ if(foundNode != null)
+ {
+ candidates.Add(foundNode);
+ }
+ }
+ FilteredResults = candidates;
+ }
+
+ internal void PopulateAutoCompleteCandidates()
+ {
+ if(PortViewModel == null) return;
+
+ var searchElements = GetMatchingNodes();
+ FilteredResults = searchElements.Select(e =>
+ {
+ var vm = new NodeSearchElementViewModel(e, this);
+ vm.RequestBitmapSource += SearchViewModelRequestBitmapSource;
+ return vm;
+ });
+ }
+
+ ///
+ /// Returns a collection of node search elements for nodes
+ /// that output a type compatible with the port type if it's an input port.
+ /// These search elements can belong to either zero touch, NodeModel or Builtin nodes.
+ /// This method returns an empty collection if the input port type cannot be inferred or
+ /// there are no matching nodes found for the type. Currently the match is an exact match
+ /// done including the rank information in the type, e.g. Point[] or var[]..[].
+ /// The search elements can be made to appear in the node autocomplete search dialog.
+ ///
+ /// collection of node search elements
+ internal IEnumerable GetMatchingNodes()
+ {
+ var elements = new List();
+
+ var inputPortType = PortViewModel.PortModel.GetInputPortType();
+ if (inputPortType == null) return elements;
+
+ var libraryServices = dynamoViewModel.Model.LibraryServices;
+
+ // Builtin functions and zero-touch functions
+ // Multi-return ports for these nodes do not contain type information
+ // and are therefore skipped.
+ var functionGroups = libraryServices.GetAllFunctionGroups();
+ var functionDescriptors = functionGroups.SelectMany(fg => fg.Functions).Where(fd => fd.IsVisibleInLibrary);
+
+ foreach (var descriptor in functionDescriptors)
+ {
+ if (descriptor.ReturnType.ToString() == inputPortType)
+ {
+ elements.Add(new ZeroTouchSearchElement(descriptor));
+ }
+ }
+
+ // NodeModel nodes
+ foreach (var element in Model.SearchEntries.OfType())
+ {
+ if (element.OutputParameters.Any(op => op == inputPortType))
+ elements.Add(element);
+ }
+
+ return elements;
+ }
+ }
+}
diff --git a/src/DynamoCoreWpf/ViewModels/Search/NodeSearchElementViewModel.cs b/src/DynamoCoreWpf/ViewModels/Search/NodeSearchElementViewModel.cs
index f7b96f50e3d..d89987bc175 100644
--- a/src/DynamoCoreWpf/ViewModels/Search/NodeSearchElementViewModel.cs
+++ b/src/DynamoCoreWpf/ViewModels/Search/NodeSearchElementViewModel.cs
@@ -42,8 +42,12 @@ public NodeSearchElementViewModel(NodeSearchElement element, SearchViewModel svm
Model.VisibilityChanged += ModelOnVisibilityChanged;
if (searchViewModel != null)
+ {
Clicked += searchViewModel.OnSearchElementClicked;
+ CreateAndConnectToPort += searchViewModel.OnRequestConnectToPort;
+ }
ClickedCommand = new DelegateCommand(OnClicked);
+ CreateAndConnectCommand = new DelegateCommand(OnRequestCreateAndConnectToPort);
LoadFonts();
}
@@ -226,6 +230,20 @@ public ImageSource LargeIcon
internal Point Position { get; set; }
+ ///
+ /// Create the search element as node and connect to target port
+ ///
+ public ICommand CreateAndConnectCommand { get; private set; }
+
+ public event Action CreateAndConnectToPort;
+ protected virtual void OnRequestCreateAndConnectToPort(PortModel portModel)
+ {
+ if (CreateAndConnectToPort != null)
+ {
+ CreateAndConnectToPort(Model.CreationName, portModel);
+ }
+ }
+
public ICommand ClickedCommand { get; private set; }
public event Action Clicked;
diff --git a/src/DynamoCoreWpf/ViewModels/Search/SearchViewModel.cs b/src/DynamoCoreWpf/ViewModels/Search/SearchViewModel.cs
index d829a410619..2c0c8db2c8f 100644
--- a/src/DynamoCoreWpf/ViewModels/Search/SearchViewModel.cs
+++ b/src/DynamoCoreWpf/ViewModels/Search/SearchViewModel.cs
@@ -18,7 +18,6 @@
using Dynamo.Utilities;
using Dynamo.Wpf.Services;
using Dynamo.Wpf.ViewModels;
-using Microsoft.Practices.Prism.ViewModel;
namespace Dynamo.ViewModels
{
@@ -300,7 +299,7 @@ public NodeSearchElementViewModel FindViewModelForNode(string nodeName)
}
public NodeSearchModel Model { get; private set; }
- private readonly DynamoViewModel dynamoViewModel;
+ internal readonly DynamoViewModel dynamoViewModel;
///
/// Class name, that has been clicked in library search view.
@@ -772,7 +771,7 @@ private void AddEntryToExistingCategory(NodeCategoryViewModel category,
}
}
- private void SearchViewModelRequestBitmapSource(IconRequestEventArgs e)
+ protected void SearchViewModelRequestBitmapSource(IconRequestEventArgs e)
{
var warehouse = iconServices.GetForAssembly(e.IconAssembly, e.UseAdditionalResolutionPaths);
ImageSource icon = null;
@@ -1060,6 +1059,18 @@ public void OnSearchElementClicked(NodeModel nodeModel, Point position)
OnRequestFocusSearch();
}
+ internal void OnRequestConnectToPort(string nodeCreationName, PortModel portModel)
+ {
+ if (!nodeCreationName.Contains("Code Block"))
+ {
+ // Create a new node based on node creation name and connect ports
+ dynamoViewModel.ExecuteCommand(new DynamoModel.CreateAndConnectNodeCommand(Guid.NewGuid(), portModel.Owner.GUID,
+ nodeCreationName, 0, portModel.Index, portModel.CenterX - 50, portModel.CenterY + 200, false, false));
+ }
+
+ OnRequestFocusSearch();
+ }
+
#endregion
#region Commands
diff --git a/src/DynamoCoreWpf/Views/Core/DynamoView.xaml b/src/DynamoCoreWpf/Views/Core/DynamoView.xaml
index 4a4362af4d8..e21a96fb5b8 100644
--- a/src/DynamoCoreWpf/Views/Core/DynamoView.xaml
+++ b/src/DynamoCoreWpf/Views/Core/DynamoView.xaml
@@ -751,6 +751,10 @@
IsCheckable="True"
IsChecked="{Binding EnableTSpline}"
Header="{x:Static p:Resources.DynamoViewSettingEnableTSplineNodes}" />
+
diff --git a/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml b/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml
index e328d16a319..e68ef3fef0a 100644
--- a/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml
+++ b/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml
@@ -345,6 +345,16 @@
+
+
+
+
diff --git a/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml.cs b/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml.cs
index be334320fe5..8f5537bbcae 100644
--- a/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml.cs
+++ b/src/DynamoCoreWpf/Views/Core/WorkspaceView.xaml.cs
@@ -108,6 +108,7 @@ void OnWorkspaceViewLoaded(object sender, RoutedEventArgs e)
DynamoSelection.Instance.Selection.CollectionChanged += OnSelectionCollectionChanged;
ViewModel.RequestShowInCanvasSearch += ShowHideInCanvasControl;
+ ViewModel.RequestNodeAutoCompleteSearch += ShowHideNodeAutoCompleteControl;
ViewModel.DynamoViewModel.PropertyChanged += ViewModel_PropertyChanged;
infiniteGridView.AttachToZoomBorder(zoomBorder);
@@ -160,6 +161,11 @@ void OnWorkspaceViewUnloaded(object sender, RoutedEventArgs e)
}
+ private void ShowHideNodeAutoCompleteControl(ShowHideFlags flag)
+ {
+ ShowHidePopup(flag, NodeAutoCompleteSearchBar);
+ }
+
private void ShowHideInCanvasControl(ShowHideFlags flag)
{
ShowHidePopup(flag, InCanvasSearchBar);
@@ -196,6 +202,16 @@ public void HidePopUp()
ShowHideContextMenu(ShowHideFlags.Hide);
ShowHideInCanvasControl(ShowHideFlags.Hide);
}
+ if (NodeAutoCompleteSearchBar.IsOpen)
+ {
+ // Suppress the mouse action from last 0.2 second otherwise mouse button release
+ // in Dynamo window will forcefully shutdown all the open SearchBars
+ if ((new TimeSpan(DateTime.Now.Ticks - ViewModel.GetLastStateTimestamp().Ticks)).TotalSeconds > 0.2)
+ {
+ ShowHideNodeAutoCompleteControl(ShowHideFlags.Hide);
+ ViewModel.CancelActiveState();
+ }
+ }
}
internal Point GetCenterPoint()
diff --git a/src/Libraries/CoreNodeModels/ColorRange.cs b/src/Libraries/CoreNodeModels/ColorRange.cs
index 223dd6f90e8..99c9ab8d08f 100644
--- a/src/Libraries/CoreNodeModels/ColorRange.cs
+++ b/src/Libraries/CoreNodeModels/ColorRange.cs
@@ -20,7 +20,7 @@ namespace CoreNodeModels
[NodeSearchTags("ColorRangeSearchTags", typeof(Resources))]
[InPortNames("colors", "indices", "value")]
- [InPortTypes("Color[]", "double[]", "double")]
+ [InPortTypes("DSCore.Color[]", "double[]", "double")]
[InPortDescriptions(typeof(Resources),
"ColorRangePortDataColorsToolTip",
"ColorRangePortDataIndicesToolTip",
diff --git a/src/Libraries/CoreNodeModels/WatchImageCore.cs b/src/Libraries/CoreNodeModels/WatchImageCore.cs
index d00e0331085..a04d785e58b 100644
--- a/src/Libraries/CoreNodeModels/WatchImageCore.cs
+++ b/src/Libraries/CoreNodeModels/WatchImageCore.cs
@@ -14,6 +14,7 @@ namespace CoreNodeModels
[NodeCategory(BuiltinNodeCategories.CORE_VIEW)]
[NodeSearchTags("WatchImageSearchTags", typeof(Resources))]
[IsDesignScriptCompatible]
+ [InPortTypes("System.Drawing.Bitmap")]
[OutPortTypes("var")]
[AlsoKnownAs("Dynamo.Nodes.WatchImageCore", "DSCoreNodesUI.WatchImageCore")]
public class WatchImageCore : NodeModel
diff --git a/src/Libraries/CoreNodes/List.cs b/src/Libraries/CoreNodes/List.cs
index 3654a884499..e4d27e5cdb7 100644
--- a/src/Libraries/CoreNodes/List.cs
+++ b/src/Libraries/CoreNodes/List.cs
@@ -304,7 +304,6 @@ public static IList Reverse(IList list)
/// Creates a new list containing the given items.
///
/// Items to be stored in the new list.
- [IsVisibleInDynamoLibrary(true)]
public static IList __Create(IList items)
{
return items;
diff --git a/src/Libraries/DesignScriptBuiltin/IndexingExceptions.cs b/src/Libraries/DesignScriptBuiltin/IndexingExceptions.cs
index 687d4c20d37..b5f72ddfac8 100644
--- a/src/Libraries/DesignScriptBuiltin/IndexingExceptions.cs
+++ b/src/Libraries/DesignScriptBuiltin/IndexingExceptions.cs
@@ -1,7 +1,9 @@
using System;
+using Autodesk.DesignScript.Runtime;
namespace DesignScript.Builtin
{
+ [SupressImportIntoVM]
public class KeyNotFoundException : Exception
{
public KeyNotFoundException(string message)
@@ -10,6 +12,7 @@ public KeyNotFoundException(string message)
}
}
+ [SupressImportIntoVM]
public class IndexOutOfRangeException : Exception
{
public IndexOutOfRangeException(string message)
@@ -18,6 +21,7 @@ public IndexOutOfRangeException(string message)
}
}
+ [SupressImportIntoVM]
public class StringOverIndexingException : Exception
{
public StringOverIndexingException(string message)
@@ -30,6 +34,7 @@ public StringOverIndexingException(string message)
/// Null reference exception thrown with null DS builtin types:
/// lists, dictionaries and strings.
///
+ [SupressImportIntoVM]
public class BuiltinNullReferenceException : NullReferenceException
{
public BuiltinNullReferenceException(string message)
diff --git a/test/DynamoCoreTests/Configuration/PreferenceSettingsTests.cs b/test/DynamoCoreTests/Configuration/PreferenceSettingsTests.cs
index e16a7f287f3..77219edcd1b 100644
--- a/test/DynamoCoreTests/Configuration/PreferenceSettingsTests.cs
+++ b/test/DynamoCoreTests/Configuration/PreferenceSettingsTests.cs
@@ -56,6 +56,7 @@ public void TestSettingsSerialization()
Assert.AreEqual(settings.ShowCodeBlockLineNumber, true);
Assert.AreEqual(settings.IsIronPythonDialogDisabled, false);
Assert.AreEqual(settings.ShowTabsAndSpacesInScriptEditor, false);
+ Assert.AreEqual(settings.EnableNodeAutoComplete, false);
Assert.AreEqual(settings.DefaultPythonEngine, string.Empty);
Assert.AreEqual(settings.MaxNumRecentFiles, PreferenceSettings.DefaultMaxNumRecentFiles);
@@ -68,6 +69,7 @@ public void TestSettingsSerialization()
Assert.AreEqual(settings.ShowCodeBlockLineNumber, true);
Assert.AreEqual(settings.IsIronPythonDialogDisabled, false);
Assert.AreEqual(settings.ShowTabsAndSpacesInScriptEditor, false);
+ Assert.AreEqual(settings.EnableNodeAutoComplete, false);
Assert.AreEqual(settings.DefaultPythonEngine, string.Empty);
Assert.AreEqual(settings.MaxNumRecentFiles, PreferenceSettings.DefaultMaxNumRecentFiles);
@@ -78,6 +80,7 @@ public void TestSettingsSerialization()
settings.ShowTabsAndSpacesInScriptEditor = true;
settings.DefaultPythonEngine = "CP3";
settings.MaxNumRecentFiles = 24;
+ settings.EnableNodeAutoComplete = true;
// Save
settings.Save(tempPath);
@@ -90,6 +93,7 @@ public void TestSettingsSerialization()
Assert.AreEqual(settings.ShowTabsAndSpacesInScriptEditor, true);
Assert.AreEqual(settings.DefaultPythonEngine, "CP3");
Assert.AreEqual(settings.MaxNumRecentFiles, 24);
+ Assert.AreEqual(settings.EnableNodeAutoComplete, true);
}
}
}
diff --git a/test/DynamoCoreWpfTests/CoreUITests.cs b/test/DynamoCoreWpfTests/CoreUITests.cs
index 2dc20cb61a5..1865951c907 100644
--- a/test/DynamoCoreWpfTests/CoreUITests.cs
+++ b/test/DynamoCoreWpfTests/CoreUITests.cs
@@ -886,6 +886,26 @@ public void WorkspaceContextMenu_IfSubmenuOpenOnMouseHover()
Assert.IsTrue(currentWs.WorkspaceLacingMenu.IsSubmenuOpen);
}
+ [Test]
+ [Category("UnitTests")]
+ public void ShowHideNodeAutoCompleteSearchControl()
+ {
+ var currentWs = View.ChildOfType();
+
+ // Show Node AutoCompleteSearchBar
+ ViewModel.CurrentSpaceViewModel.OnRequestNodeAutoCompleteSearch(ShowHideFlags.Show);
+ Assert.IsTrue(currentWs.NodeAutoCompleteSearchBar.IsOpen);
+
+ RightClick(currentWs.zoomBorder);
+ // Notice AutoCompleteSearchBar can co-exist with right click search for now
+ Assert.IsTrue(currentWs.ContextMenuPopup.IsOpen);
+ Assert.IsTrue(currentWs.NodeAutoCompleteSearchBar.IsOpen);
+
+ // Hide Node AutoCompleteSearchBar
+ ViewModel.CurrentSpaceViewModel.OnRequestNodeAutoCompleteSearch(ShowHideFlags.Hide);
+ Assert.IsFalse(currentWs.NodeAutoCompleteSearchBar.IsOpen);
+ }
+
private void RightClick(IInputElement element)
{
element.RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Right)
diff --git a/test/DynamoCoreWpfTests/DynamoCoreWpfTests.csproj b/test/DynamoCoreWpfTests/DynamoCoreWpfTests.csproj
index d91cc0f0b0f..93cb3094055 100644
--- a/test/DynamoCoreWpfTests/DynamoCoreWpfTests.csproj
+++ b/test/DynamoCoreWpfTests/DynamoCoreWpfTests.csproj
@@ -126,6 +126,7 @@
+
diff --git a/test/DynamoCoreWpfTests/NodeAutoCompleteSearchTests.cs b/test/DynamoCoreWpfTests/NodeAutoCompleteSearchTests.cs
new file mode 100644
index 00000000000..3d808390c97
--- /dev/null
+++ b/test/DynamoCoreWpfTests/NodeAutoCompleteSearchTests.cs
@@ -0,0 +1,122 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Dynamo.Controls;
+using Dynamo.ViewModels;
+using DynamoCoreWpfTests.Utility;
+using NUnit.Framework;
+
+namespace DynamoCoreWpfTests
+{
+ class NodeAutoCompleteSearchTests : DynamoTestUIBase
+ {
+ protected override void GetLibrariesToPreload(List libraries)
+ {
+ libraries.Add("FunctionObject.ds");
+ libraries.Add("BuiltIn.ds");
+ libraries.Add("FFITarget.dll");
+ }
+
+ public override void Open(string path)
+ {
+ base.Open(path);
+
+ DispatcherUtil.DoEvents();
+ }
+
+ public override void Run()
+ {
+ base.Run();
+
+ DispatcherUtil.DoEvents();
+ }
+
+
+ [Test]
+ public void NodeSuggestions_InputPortZeroTouchNode_AreCorrect()
+ {
+ Open(@"UI\ffitarget_inputport_suggestion.dyn");
+
+ // Get the node view for a specific node in the graph
+ NodeView nodeView = NodeViewWithGuid(Guid.Parse("9aeba33453a34c73823976222b44375b").ToString());
+
+ var inPorts = nodeView.ViewModel.InPorts;
+ Assert.AreEqual(2, inPorts.Count());
+
+ var port = inPorts[0].PortModel;
+ var type = port.GetInputPortType();
+ Assert.AreEqual("FFITarget.DummyPoint", type);
+
+ port = inPorts[1].PortModel;
+ type = port.GetInputPortType();
+ Assert.AreEqual("FFITarget.DummyVector", type);
+
+ var searchViewModel = (ViewModel.CurrentSpaceViewModel.NodeAutoCompleteSearchViewModel as NodeAutoCompleteSearchViewModel);
+ searchViewModel.PortViewModel = inPorts[1];
+ var suggestions = searchViewModel.GetMatchingNodes();
+ Assert.AreEqual(5, suggestions.Count());
+
+ var suggestedNodes = suggestions.Select(s => s.FullName).OrderBy(s => s);
+ var nodes = new[] { "FFITarget.FFITarget.DummyVector.DummyVector", "FFITarget.FFITarget.DummyVector.ByCoordinates",
+ "FFITarget.FFITarget.DummyVector.ByVector", "FFITarget.FFITarget.DummyVector.Scale", "FFITarget.FFITarget.DummyPoint.DirectionTo" };
+ var expectedNodes = nodes.OrderBy(s => s);
+ for (int i = 0; i < 5; i++)
+ {
+ Assert.AreEqual(expectedNodes.ElementAt(i), suggestedNodes.ElementAt(i));
+ }
+ }
+
+ [Test]
+ public void NodeSuggestions_InputPortBuiltInNode_AreCorrect()
+ {
+ Open(@"UI\builtin_inputport_suggestion.dyn");
+
+ // Get the node view for a specific node in the graph
+ NodeView nodeView = NodeViewWithGuid(Guid.Parse("b6cb6ceb21df4c7fb6b186e6ff399afc").ToString());
+
+ var inPorts = nodeView.ViewModel.InPorts;
+ Assert.AreEqual(2, inPorts.Count());
+
+ var port = inPorts[0].PortModel;
+ var type = port.GetInputPortType();
+ Assert.AreEqual("var[]..[]", type);
+
+ port = inPorts[1].PortModel;
+ type = port.GetInputPortType();
+ Assert.AreEqual("string", type);
+
+ var searchViewModel = (ViewModel.CurrentSpaceViewModel.NodeAutoCompleteSearchViewModel as NodeAutoCompleteSearchViewModel);
+ searchViewModel.PortViewModel = inPorts[1];
+ var suggestions = searchViewModel.GetMatchingNodes();
+ Assert.AreEqual(16, suggestions.Count());
+
+ var suggestedNodes = suggestions.Select(s => s.FullName).OrderBy(s => s);
+ var nodes = new[]
+ {
+ "Core.Input.File Path",
+ "Core.String.String from Array",
+ "Core.String.String from Object",
+ "FFITarget.DupTargetTest.Bar",
+ "FFITarget.FFITarget.AtLevelTestClass.sumAndConcat",
+ "FFITarget.FFITarget.AtLevelTestClass.SumAndConcat",
+ "FFITarget.FFITarget.FirstNamespace.AnotherClassWithNameConflict.PropertyA",
+ "FFITarget.FFITarget.FirstNamespace.AnotherClassWithNameConflict.PropertyB",
+ "FFITarget.FFITarget.FirstNamespace.AnotherClassWithNameConflict.PropertyC",
+ "FFITarget.FFITarget.FirstNamespace.ClassWithNameConflict.PropertyA",
+ "FFITarget.FFITarget.FirstNamespace.ClassWithNameConflict.PropertyB",
+ "FFITarget.FFITarget.FirstNamespace.ClassWithNameConflict.PropertyC",
+ "FFITarget.FFITarget.SecondNamespace.ClassWithNameConflict.PropertyD",
+ "FFITarget.FFITarget.SecondNamespace.ClassWithNameConflict.PropertyE",
+ "FFITarget.FFITarget.SecondNamespace.ClassWithNameConflict.PropertyF",
+ "FFITarget.FFITarget.TestData.GetStringValue"
+ };
+ var expectedNodes = nodes.OrderBy(s => s);
+ for (int i = 0; i < 5; i++)
+ {
+ Assert.AreEqual(expectedNodes.ElementAt(i), suggestedNodes.ElementAt(i));
+ }
+ }
+ }
+}
diff --git a/test/DynamoCoreWpfTests/NodeViewTests.cs b/test/DynamoCoreWpfTests/NodeViewTests.cs
index e6f9f2c30a6..873bb8de87f 100644
--- a/test/DynamoCoreWpfTests/NodeViewTests.cs
+++ b/test/DynamoCoreWpfTests/NodeViewTests.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -17,6 +18,13 @@ public class NodeViewTests : DynamoTestUIBase
{
// adapted from: http://stackoverflow.com/questions/9336165/correct-method-for-using-the-wpf-dispatcher-in-unit-tests
+ protected override void GetLibrariesToPreload(List libraries)
+ {
+ libraries.Add("FunctionObject.ds");
+ libraries.Add("BuiltIn.ds");
+ libraries.Add("FFITarget.dll");
+ }
+
public override void Open(string path)
{
base.Open(path);
@@ -327,6 +335,22 @@ public void SettingOriginalNodeNameOnCustomNode()
Assert.AreEqual(nodeViewModel.OriginalName, expectedOriginalName);
}
+ [Test]
+ public void InputPortType_NodeModelNode_AreCorrect()
+ {
+ Open(@"UI\CoreUINodes.dyn");
+
+ // Get the node view for a specific node in the graph
+ NodeView nodeView = NodeViewWithGuid(Guid.Parse("9dedd5c5c8b14fbebaea28194fd38c9a").ToString());
+
+ var inPorts = nodeView.ViewModel.InPorts;
+ Assert.AreEqual(1, inPorts.Count());
+
+ var port = inPorts[0].PortModel;
+ var type = port.GetInputPortType();
+ Assert.AreEqual("System.Drawing.Bitmap", type);
+ }
+
[Test]
[Category("RegressionTests")]
public void GettingNodeNameDoesNotTriggerPropertyChangeCycle()
diff --git a/test/UI/builtin_inputport_suggestion.dyn b/test/UI/builtin_inputport_suggestion.dyn
new file mode 100644
index 00000000000..0568990ceea
--- /dev/null
+++ b/test/UI/builtin_inputport_suggestion.dyn
@@ -0,0 +1,94 @@
+{
+ "Uuid": "87442094-a012-4db1-9432-789dd1c20ba8",
+ "IsCustomNode": false,
+ "Description": null,
+ "Name": "builtin_inputport_suggestion",
+ "ElementResolver": {
+ "ResolutionMap": {}
+ },
+ "Inputs": [],
+ "Outputs": [],
+ "Nodes": [
+ {
+ "ConcreteType": "Dynamo.Graph.Nodes.ZeroTouch.DSFunction, DynamoCore",
+ "NodeType": "FunctionNode",
+ "FunctionSignature": "List.RemoveIfNot@var[]..[],string",
+ "Id": "b6cb6ceb21df4c7fb6b186e6ff399afc",
+ "Inputs": [
+ {
+ "Id": "b090a79fa5914f049ad2fbc48d7fbf74",
+ "Name": "list",
+ "Description": "list of values\n\nvar[]..[]",
+ "UsingDefaultValue": false,
+ "Level": 2,
+ "UseLevels": false,
+ "KeepListStructure": false
+ },
+ {
+ "Id": "9c013fa7a7f84505abbba72579e32545",
+ "Name": "type",
+ "Description": "type of element\n\nstring",
+ "UsingDefaultValue": false,
+ "Level": 2,
+ "UseLevels": false,
+ "KeepListStructure": false
+ }
+ ],
+ "Outputs": [
+ {
+ "Id": "1211bcfe948c46a39eacb248569c4cce",
+ "Name": "var[]..[]",
+ "Description": "var[]..[]",
+ "UsingDefaultValue": false,
+ "Level": 2,
+ "UseLevels": false,
+ "KeepListStructure": false
+ }
+ ],
+ "Replication": "Auto",
+ "Description": "Removes the members of the list which are not members of the specified type.\n\nList.RemoveIfNot (list: var[]..[], type: string): var[]..[]"
+ }
+ ],
+ "Connectors": [],
+ "Dependencies": [],
+ "NodeLibraryDependencies": [],
+ "Bindings": [],
+ "View": {
+ "Dynamo": {
+ "ScaleFactor": 1.0,
+ "HasRunWithoutCrash": true,
+ "IsVisibleInDynamoLibrary": true,
+ "Version": "2.9.0.2607",
+ "RunType": "Automatic",
+ "RunPeriod": "1000"
+ },
+ "Camera": {
+ "Name": "Background Preview",
+ "EyeX": -17.0,
+ "EyeY": 24.0,
+ "EyeZ": 50.0,
+ "LookX": 12.0,
+ "LookY": -13.0,
+ "LookZ": -58.0,
+ "UpX": 0.0,
+ "UpY": 1.0,
+ "UpZ": 0.0
+ },
+ "NodeViews": [
+ {
+ "ShowGeometry": true,
+ "Name": "List.RemoveIfNot",
+ "Id": "b6cb6ceb21df4c7fb6b186e6ff399afc",
+ "IsSetAsInput": false,
+ "IsSetAsOutput": false,
+ "Excluded": false,
+ "X": 505.0,
+ "Y": 274.0
+ }
+ ],
+ "Annotations": [],
+ "X": 0.0,
+ "Y": 0.0,
+ "Zoom": 1.0
+ }
+}
\ No newline at end of file
diff --git a/test/UI/ffitarget_inputport_suggestion.dyn b/test/UI/ffitarget_inputport_suggestion.dyn
new file mode 100644
index 00000000000..b8ba88766be
--- /dev/null
+++ b/test/UI/ffitarget_inputport_suggestion.dyn
@@ -0,0 +1,94 @@
+{
+ "Uuid": "79ea0296-2698-4563-9ebd-ca50128bd4e7",
+ "IsCustomNode": false,
+ "Description": null,
+ "Name": "ffitarget_inputport_suggestion",
+ "ElementResolver": {
+ "ResolutionMap": {}
+ },
+ "Inputs": [],
+ "Outputs": [],
+ "Nodes": [
+ {
+ "ConcreteType": "Dynamo.Graph.Nodes.ZeroTouch.DSFunction, DynamoCore",
+ "NodeType": "FunctionNode",
+ "FunctionSignature": "FFITarget.DummyPoint.Translate@FFITarget.DummyVector",
+ "Id": "9aeba33453a34c73823976222b44375b",
+ "Inputs": [
+ {
+ "Id": "6386befce6fb4baba5131314fc55348b",
+ "Name": "dummyPoint",
+ "Description": "FFITarget.DummyPoint",
+ "UsingDefaultValue": false,
+ "Level": 2,
+ "UseLevels": false,
+ "KeepListStructure": false
+ },
+ {
+ "Id": "4352cc5bbe58469e80d7db2760dd0871",
+ "Name": "direction",
+ "Description": "DummyVector",
+ "UsingDefaultValue": false,
+ "Level": 2,
+ "UseLevels": false,
+ "KeepListStructure": false
+ }
+ ],
+ "Outputs": [
+ {
+ "Id": "fc8a45bf13a449b4814ec0a843fc5be0",
+ "Name": "DummyPoint",
+ "Description": "DummyPoint",
+ "UsingDefaultValue": false,
+ "Level": 2,
+ "UseLevels": false,
+ "KeepListStructure": false
+ }
+ ],
+ "Replication": "Auto",
+ "Description": "DummyPoint.Translate (direction: DummyVector): DummyPoint"
+ }
+ ],
+ "Connectors": [],
+ "Dependencies": [],
+ "NodeLibraryDependencies": [],
+ "Bindings": [],
+ "View": {
+ "Dynamo": {
+ "ScaleFactor": 1.0,
+ "HasRunWithoutCrash": true,
+ "IsVisibleInDynamoLibrary": true,
+ "Version": "2.9.0.2607",
+ "RunType": "Automatic",
+ "RunPeriod": "1000"
+ },
+ "Camera": {
+ "Name": "Background Preview",
+ "EyeX": -17.0,
+ "EyeY": 24.0,
+ "EyeZ": 50.0,
+ "LookX": 12.0,
+ "LookY": -13.0,
+ "LookZ": -58.0,
+ "UpX": 0.0,
+ "UpY": 1.0,
+ "UpZ": 0.0
+ },
+ "NodeViews": [
+ {
+ "ShowGeometry": true,
+ "Name": "DummyPoint.Translate",
+ "Id": "9aeba33453a34c73823976222b44375b",
+ "IsSetAsInput": false,
+ "IsSetAsOutput": false,
+ "Excluded": false,
+ "X": 618.0,
+ "Y": 357.0
+ }
+ ],
+ "Annotations": [],
+ "X": 0.0,
+ "Y": 0.0,
+ "Zoom": 1.0
+ }
+}
\ No newline at end of file