Skip to content

Commit

Permalink
Resolve "Add Search Bar to Config Views"
Browse files Browse the repository at this point in the history
Closes #1982

See merge request main/Sumatra!1873

sumatra-commit: e527f3919a8a8503f1b6fdecc9ad3c8289e586e0
  • Loading branch information
g3force authored and TIGERs GitLab committed Jul 14, 2024
1 parent 59b446f commit 7ba31b4
Show file tree
Hide file tree
Showing 13 changed files with 446 additions and 163 deletions.
4 changes: 3 additions & 1 deletion modules/common-gui-config/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ plugins {

dependencies {
implementation project(':common-gui')
implementation project(':sumatra-model')

implementation(libs.com.github.g3force.configurable)
implementation(libs.com.github.g3force.string2ValueConverter)

implementation(libs.commons.configuration)
implementation(libs.com.miglayout.miglayout.swing)

implementation 'me.xdrop:fuzzywuzzy:1.4.0'
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,27 @@

package edu.tigers.sumatra.config;

import net.miginfocom.swing.MigLayout;
import me.xdrop.fuzzywuzzy.FuzzySearch;
import org.apache.commons.configuration.HierarchicalConfiguration;
import org.apache.commons.configuration.tree.ConfigurationNode;

import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.SwingConstants;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.BorderLayout;
import java.awt.Component;
import java.io.Serial;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Objects;
import java.util.Optional;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;


/**
Expand All @@ -26,19 +37,28 @@ public class ConfigEditorPanel extends JPanel

private final JTabbedPane tabpane;
private final SortedMap<String, EditorView> tabs = new TreeMap<>();
private final ConfigEditorSearchBar searchBar = new ConfigEditorSearchBar();

private String searchTokens = "";
private String latestName = "";
private HierarchicalConfiguration latestConfig = null;


public ConfigEditorPanel()
{
setLayout(new MigLayout("fill, wrap 1, inset 0"));
setLayout(new BorderLayout());

tabpane = new JTabbedPane(SwingConstants.TOP, JTabbedPane.WRAP_TAB_LAYOUT);
tabpane.addChangeListener(e -> {
Component c = tabpane.getComponentAt(tabpane.getSelectedIndex());
EditorView ev = (EditorView) c;
ev.initialReload();
});
add(tabpane, "grow");

searchBar.getTextField().getDocument().addDocumentListener(new SearchBarListener());

add(searchBar, BorderLayout.NORTH);
add(tabpane, BorderLayout.CENTER);
}


Expand All @@ -52,8 +72,8 @@ public void addConfigModel(final String client, final IConfigEditorViewObserver
{
return;
}
final EditorView newView = new EditorView(client, client, new HierarchicalConfiguration(),
true);
final EditorView newView = new EditorView(client, client, new HierarchicalConfiguration()
);
newView.addObserver(observer);

String configKey = newView.getConfigKey();
Expand All @@ -79,9 +99,126 @@ public void addConfigModel(final String client, final IConfigEditorViewObserver
* @param name
* @param config
*/
public void refreshConfigModel(final String name, final HierarchicalConfiguration config)
public void refreshConfigModel(String name, HierarchicalConfiguration config)
{
latestName = name;
latestConfig = config;

var view = tabs.get(name);
var model = new ConfigXMLTreeTableModel(config);

if (!searchTokens.isEmpty())
{
view.updateModel(filterModel(config, model), true);
} else
{
view.updateModel(model, false);
}
}


private ConfigXMLTreeTableModel filterModel(HierarchicalConfiguration config, ConfigXMLTreeTableModel model)
{
var filteredConfig = new HierarchicalConfiguration();
var filteredRoot = filterConfigNode(config.getRootNode(), model, false).map(FilterResult::node);
filteredRoot.ifPresent(filteredConfig::setRootNode);
return new ConfigXMLTreeTableModel(filteredConfig);
}


private Optional<FilterResult> filterConfigNode(ConfigurationNode originalNode, ConfigXMLTreeTableModel model,
boolean someParentIsMatching)
{
int score = matchingScore(originalNode, model);
boolean matching = someParentIsMatching || score >= 85;
var newChildren = originalNode.getChildren().stream()
.map(node -> filterConfigNode(node, model, someParentIsMatching || matching))
.flatMap(Optional::stream)
.sorted(Comparator.comparingInt(FilterResult::matchingScore).reversed())
.toList();

if (newChildren.isEmpty() && !matching)
{
return Optional.empty();
}

var newNode = new HierarchicalConfiguration.Node(originalNode.getName(), originalNode.getValue());
newNode.setReference(originalNode.getReference());
newChildren.forEach(child -> newNode.addChild(child.node));

var newAttributes = originalNode.getAttributes().stream()
.map(node -> filterConfigNode(node, model, true))
.flatMap(Optional::stream)
.toList();
newAttributes.forEach(attrib -> newNode.addAttribute(attrib.node));
return Optional.of(new FilterResult(score, newNode));
}


private int matchingScore(ConfigurationNode node, ConfigXMLTreeTableModel model)
{
var stringsToCheck = Stream.concat(
Stream.of(node.getName()),
IntStream.range(0, model.getColumnCount())
.mapToObj(i -> model.getValueAt(node, i))
.filter(Objects::nonNull)
.filter(String.class::isInstance)
.map(String.class::cast)
);

var description = stringsToCheck
.map(s -> s.split(" "))
.flatMap(Arrays::stream)
.map(String::strip)
.filter(s -> !s.isEmpty())
.map(String::toLowerCase)
.collect(Collectors.joining(" "));

return FuzzySearch.tokenSetPartialRatio(description, searchTokens);
}


private record FilterResult(int matchingScore, ConfigurationNode node)
{
}


private class SearchBarListener implements DocumentListener
{
final EditorView view = tabs.get(name);
view.updateModel(config, true);

@Override
public void insertUpdate(DocumentEvent documentEvent)
{
applySearch();
}


@Override
public void removeUpdate(DocumentEvent documentEvent)
{
applySearch();
}


@Override
public void changedUpdate(DocumentEvent documentEvent)
{
applySearch();
}


private void applySearch()
{
searchTokens = Arrays.stream(searchBar.getTextField().getText().split(" "))
.map(String::toLowerCase)
.map(String::strip)
.filter(s -> !s.isEmpty())
.collect(Collectors.joining(" "));
if (latestConfig != null && latestName != null && tabs.get(latestName) != null)
{
refreshConfigModel(latestName, latestConfig);
}
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2009 - 2024, DHBW Mannheim - TIGERs Mannheim
*/

package edu.tigers.sumatra.config;

import edu.tigers.sumatra.util.ImageScaler;
import lombok.Getter;

import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JTextField;
import java.awt.BorderLayout;


public class ConfigEditorSearchBar extends JComponent
{
@Getter
private final JTextField textField = new JTextField();


public ConfigEditorSearchBar()
{

setLayout(new BorderLayout());
var icon = new JLabel();
icon.setIcon(ImageScaler.scaleSmallButtonImageIcon("/icons8-search-50.png"));


add(icon, BorderLayout.WEST);
add(textField, BorderLayout.CENTER);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@

import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.tree.TreePath;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;


/**
Expand All @@ -31,14 +35,12 @@ public class ConfigXMLTreeTableModel extends ATreeTableModel


private final String2ValueConverter s2vConv = String2ValueConverter.getDefault();
private final HierarchicalConfiguration config;


public ConfigXMLTreeTableModel(final HierarchicalConfiguration xml)
{
// Hopefully there is no comment as first element... :-P
super(xml.getRoot());
config = xml;
}


Expand Down Expand Up @@ -140,39 +142,6 @@ public int getChildCount(final Object obj)
}


@Override
public boolean isCellEditable(final Object obj, final int col)
{
// 0.0 = "Name"
if (col == 0)
{
// For tree-expansion/collapse
return super.isCellEditable(obj, col);
}

if (!isEditable())
{
// Editing disabled
return false;
}

switch (col)
{

// "Value"
case 1:
return isLeaf(obj);

// "Comment"
case 2:
final org.w3c.dom.Node comment = getComment(obj);
return comment != null;
default:
throw new IllegalArgumentException();
}
}


@Override
public void setValueAt(final Object value, final Object obj, final int col)
{
Expand Down Expand Up @@ -280,6 +249,23 @@ private org.w3c.dom.Node getComment(final Object obj)
}


@Override
protected Stream<TreePath> getAllTreePaths(Object node, List<Object> parentPath)
{
if (node instanceof ConfigurationNode configNode)
{
var currentPath = new ArrayList<>(parentPath);
currentPath.add(configNode);

return Stream.concat(
configNode.getChildren().stream().flatMap(child -> getAllTreePaths(child, currentPath)),
Stream.of(new TreePath(currentPath.toArray()))
);
}
return Stream.of();
}


@Override
public int getColumnCount()
{
Expand All @@ -299,4 +285,15 @@ public Class<?> getColumnClass(final int col)
{
return CLASSES[col];
}


@Override
protected Optional<String> nodeToString(Object node)
{
if (node instanceof ConfigurationNode configNode)
{
return Optional.ofNullable(configNode.getName());
}
return Optional.empty();
}
}
Loading

0 comments on commit 7ba31b4

Please sign in to comment.