Skip to content

Commit

Permalink
More graph utils
Browse files Browse the repository at this point in the history
  • Loading branch information
VoidXH committed May 20, 2024
1 parent 7927cda commit b608dcd
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 20 deletions.
40 changes: 40 additions & 0 deletions Cavern/Filters/Utilities/FilterGraphNodeUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,27 @@ namespace Cavern.Filters.Utilities {
/// Special functions for handling <see cref="FilterGraphNode"/>s.
/// </summary>
public static class FilterGraphNodeUtils {
/// <summary>
/// Check if the graph has cycles.
/// </summary>
/// <param name="rootNodes">All nodes which have no parents</param>
public static bool HasCycles(IEnumerable<FilterGraphNode> rootNodes) {
HashSet<FilterGraphNode> visited = new HashSet<FilterGraphNode>(),
inProgress = new HashSet<FilterGraphNode>();
foreach (FilterGraphNode node in rootNodes) {
if (!visited.Contains(node)) {
if (HasCycles(node, visited, inProgress)) {
return true;
}
}
}
return false;
}

/// <summary>
/// Get all nodes in a filter graph knowing the root nodes.
/// </summary>
/// <param name="rootNodes">All nodes which have no parents</param>
public static HashSet<FilterGraphNode> MapGraph(IEnumerable<FilterGraphNode> rootNodes) {
HashSet<FilterGraphNode> visited = new HashSet<FilterGraphNode>();
Queue<FilterGraphNode> queue = new Queue<FilterGraphNode>(rootNodes);
Expand All @@ -25,5 +43,27 @@ public static HashSet<FilterGraphNode> MapGraph(IEnumerable<FilterGraphNode> roo

return visited;
}

/// <summary>
/// Starting from a single node, checks if the graph has cycles.
/// </summary>
static bool HasCycles(FilterGraphNode currentNode, HashSet<FilterGraphNode> visited, HashSet<FilterGraphNode> inProgress) {
if (inProgress.Contains(currentNode)) {
return true;
}
if (visited.Contains(currentNode)) {
return false;
}

inProgress.Add(currentNode);
foreach (FilterGraphNode child in currentNode.Children) {
if (HasCycles(child, visited, inProgress)) {
return true;
}
}
inProgress.Remove(currentNode);
visited.Add(currentNode);
return false;
}
}
}
19 changes: 19 additions & 0 deletions CavernSamples/FilterStudio/Graphs/ManipulatableGraph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,21 @@ public class ManipulatableGraph : ScrollViewer {
/// </summary>
public event Action<object> OnRightClick;

/// <summary>
/// When the user connects a parent (first parameter) and child (second parameter) nodes, this function is called.
/// </summary>
public event Action<StyledNode, StyledNode> OnConnect;

/// <summary>
/// The currently selected node is determined by border line thickness.
/// </summary>
public StyledNode SelectedNode => (StyledNode)viewer.Graph?.Nodes.FirstOrDefault(x => x.Attr.LineWidth > 1);

/// <summary>
/// The user started dragging from this node.
/// </summary>
StyledNode dragStart;

/// <summary>
/// Handle to MSAGL.
/// </summary>
Expand Down Expand Up @@ -95,6 +105,9 @@ protected override void OnPreviewMouseUp(MouseButtonEventArgs e) {
IViewerObject element = viewer.ObjectUnderMouseCursor;
object param = null;
if (element is IViewerNode vnode) {
if (dragStart != null && vnode.Node != dragStart) {
OnConnect?.Invoke(dragStart, (StyledNode)vnode.Node);
}
param = vnode.Node;
} else if (element is IViewerEdge edge) {
param = edge.Edge;
Expand All @@ -121,5 +134,11 @@ protected override void OnPreviewMouseMove(MouseEventArgs e) {
e.Handled = true;
}
}

/// <summary>
/// Starts to track dragging a new edge from a node.
/// </summary>
protected override void OnPreviewMouseDown(MouseButtonEventArgs e) =>
dragStart = (StyledNode)(viewer.ObjectUnderMouseCursor as IViewerNode)?.Node;
}
}
59 changes: 58 additions & 1 deletion CavernSamples/FilterStudio/MainWindow.Graph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using System.Collections.Generic;
using System.Windows;

using Cavern.Filters;
using Cavern.Filters.Utilities;
using VoidX.WPF;

using FilterStudio.Graphs;
Expand Down Expand Up @@ -52,6 +54,33 @@ void SetDirection(LayerDirection direction) {
/// </summary>
void Recenter(object _, RoutedEventArgs e) => ReloadGraph();

/// <summary>
/// Delete the currently selected node.
/// </summary>
void DeleteNode(object sender, RoutedEventArgs e) {
StyledNode node = graph.GetSelectedNode(sender);
if (node == null || node.Filter == null) {
Error((string)language["NFNod"]);
} else if (node.Filter.Filter is InputChannel) {
Error((string)language["NFInp"]);
} else if (node.Filter.Filter is OutputChannel) {
Error((string)language["NFOut"]);
} else {
node.Filter.DetachFromGraph();
ReloadGraph();
}
}

/// <summary>
/// Delete the selected edge.
/// </summary>
void DeleteEdge(Edge edge) {
FilterGraphNode parent = ((StyledNode)edge.SourceNode).Filter,
child = ((StyledNode)edge.TargetNode).Filter;
parent.DetachChild(child, false);
ReloadGraph();
}

/// <summary>
/// When selecting a node, open it for modification.
/// </summary>
Expand All @@ -71,19 +100,47 @@ void GraphLeftClick(object _) {
/// Display the context menu when the graph is right clicked.
/// </summary>
void GraphRightClick(object element) {
if (element is not Node && element is not Edge) {
return;
}

List<(string, Action<object, RoutedEventArgs>)> menuItems = [
((string)language["FLabe"], (_, e) => AddLabel(element, e)),
((string)language["FGain"], (_, e) => AddGain(element, e)),
((string)language["FDela"], (_, e) => AddDelay(element, e)),
((string)language["FBiqu"], (_, e) => AddBiquad(element, e)),
(null, null) // Separator for deletion
];
if (element is Node) {
menuItems.Add((null, null));
menuItems.Add(((string)language["CoDel"], (_, e) => DeleteNode(element, e)));
} else {
menuItems.Add(((string)language["CoDel"], (_, e) => DeleteEdge((Edge)element)));
}
QuickContextMenu.Show(menuItems);
}

/// <summary>
/// Handle creating a user-selected connection.
/// </summary>
void GraphConnect(StyledNode parent, StyledNode child) {
if (child.Filter.Filter is InputChannel) {
Error((string)language["NCInp"]);
return;
}
if (parent.Filter.Filter is OutputChannel) {
Error((string)language["NCOut"]);
return;
}

parent.Filter.AddChild(child.Filter);
if (FilterGraphNodeUtils.HasCycles(rootNodes)) {
Error((string)language["NLoop"]);
parent.Filter.DetachChild(child.Filter, false);
} else {
ReloadGraph();
}
}

/// <summary>
/// Updates the graph based on the <see cref="rootNodes"/>.
/// </summary>
Expand Down
5 changes: 3 additions & 2 deletions CavernSamples/FilterStudio/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@
<fs:PipelineEditor VerticalAlignment="Top" Height="100" x:Name="pipeline"/>
<Separator Margin="0,99,0,0" VerticalAlignment="Top"/>
<fs:ManipulatableGraph Margin="0,100,0,0" x:Name="graph"/>
<TextBlock Name="help1" Margin="10,0,0,30" HorizontalAlignment="Left" VerticalAlignment="Bottom" Text="{StaticResource Help1}"/>
<TextBlock Name="help2" Margin="10,0,0,10" HorizontalAlignment="Left" VerticalAlignment="Bottom" Text="{StaticResource Help2}"/>
<TextBlock Name="help1" Margin="10,0,0,50" HorizontalAlignment="Left" VerticalAlignment="Bottom" Text="{StaticResource Help1}"/>
<TextBlock Name="help2" Margin="10,0,0,30" HorizontalAlignment="Left" VerticalAlignment="Bottom" Text="{StaticResource Help2}"/>
<TextBlock Name="help3" Margin="10,0,0,10" HorizontalAlignment="Left" VerticalAlignment="Bottom" Text="{StaticResource Help3}"/>
</Grid>
</DockPanel>
</Window>
19 changes: 2 additions & 17 deletions CavernSamples/FilterStudio/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public MainWindow() {
pipeline.language = language;
graph.OnLeftClick += GraphLeftClick;
graph.OnRightClick += GraphRightClick;
graph.OnConnect += GraphConnect;

showInstructions.IsChecked = Settings.Default.showInstructions;
SetInstructions(null, null);
Expand Down Expand Up @@ -120,30 +121,14 @@ void SelectChannels(object _, RoutedEventArgs e) {
}
}

/// <summary>
/// Delete the currently selected node.
/// </summary>
void DeleteNode(object sender, RoutedEventArgs e) {
StyledNode node = graph.GetSelectedNode(sender);
if (node == null || node.Filter == null) {
Error((string)language["NFNod"]);
} else if (node.Filter.Filter is InputChannel) {
Error((string)language["NFInp"]);
} else if (node.Filter.Filter is OutputChannel) {
Error((string)language["NFOut"]);
} else {
node.Filter.DetachFromGraph();
ReloadGraph();
}
}

/// <summary>
/// Handle when the instructions are enabled or disabled.
/// </summary>
void SetInstructions(object _, RoutedEventArgs e) {
Visibility instructions = showInstructions.IsChecked ? Visibility.Visible : Visibility.Hidden;
help1.Visibility = instructions;
help2.Visibility = instructions;
help3.Visibility = instructions;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

<system:String x:Key="Help1">Jobb klikkelj egy szűrőre, hogy új szűrőútvonalat indíts belőle.</system:String>
<system:String x:Key="Help2">Jobb klikkelj egy nyílra, hogy új szűrőt adj hozzá két szűrő közé.</system:String>
<system:String x:Key="Help3">Húzz egy szűrőt lenyomott egérgombbal a másikba, hogy összekösd őket.</system:String>

<system:String x:Key="OpFil">Equalizer APO konfigurációk|*.txt</system:String>
<system:String x:Key="Error">Hiba</system:String>
Expand All @@ -40,6 +41,9 @@ A csatornakonfiguráció csak új konfigurációs fájl létrehozásakor lesz al
<system:String x:Key="NFLas">Kimenetek után nem adható hozzá szűrő.</system:String>
<system:String x:Key="NFInp">Egy csatorna bemenete nem törölhető.</system:String>
<system:String x:Key="NFOut">Egy csatorna kimenete nem törölhető.</system:String>
<system:String x:Key="NCInp">Szűrök nem köthetők bemenetbe.</system:String>
<system:String x:Key="NCOut">Kimenetek nem köthetők szűrőkbe.</system:String>
<system:String x:Key="NLoop">Ez az összeköttetés érvénytelen, mert kört okozna a gráfban.</system:String>

<system:String x:Key="NSNew">Új</system:String>
<system:String x:Key="NInpu">Bemenet</system:String>
Expand Down
4 changes: 4 additions & 0 deletions CavernSamples/FilterStudio/Resources/MainWindowStrings.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

<system:String x:Key="Help1">Right click a filter to start a new path from it with a new filter.</system:String>
<system:String x:Key="Help2">Right click an arrow to add a filter between two filters.</system:String>
<system:String x:Key="Help3">Drag and drop from one filter to another to connect them.</system:String>

<system:String x:Key="OpFil">Equalizer APO configurations|*.txt</system:String>
<system:String x:Key="Error">Error</system:String>
Expand All @@ -40,6 +41,9 @@ The channel configuration will only be applied when you create a new configurati
<system:String x:Key="NFLas">Filters can't be added after an output.</system:String>
<system:String x:Key="NFInp">The input of a channel can't be deleted.</system:String>
<system:String x:Key="NFOut">The output of a channel can't be deleted.</system:String>
<system:String x:Key="NCInp">Filters cannot be connected to inputs.</system:String>
<system:String x:Key="NCOut">Outputs cannot be connected to filters.</system:String>
<system:String x:Key="NLoop">This connection is invalid, because it would cause a cycle in the graph.</system:String>

<system:String x:Key="NSNew">New</system:String>
<system:String x:Key="NInpu">Input</system:String>
Expand Down

0 comments on commit b608dcd

Please sign in to comment.