Поиск, который я пытаюсь реализовать, представляет собой поиск в стиле google-chrome, в котором все совпадения выделены желтым, а текущее совпадение выделено оранжевым, и можно перемещаться вперед с помощью Enter или назад с помощью Shift + Enter.Используемый мной компонент Swing - это Swing Outline Netbeans, который в основном выглядит как JTree Table.Он работает, пока контур не будет отсортирован, нажав на любой из его столбцов.В этом примере есть только один столбец.
Чтобы этот пример работал, нам понадобится контурный файл из дистрибутива Netbeans, который находится здесь: netbeans \ platform \ modules \ org-netbeans-swing-outline.jar.Другой вариант - поместить этот файл сборки Gradle (build.gradle) в корень проекта Gradle:
plugins {
id 'java-library'
}
dependencies {
compile 'uk.gov.nationalarchives.thirdparty.netbeans:org-netbeans-swing-outline:7.2'
}
repositories {
mavenCentral()
}
В древовидной модели есть функция поиска findNodesWithPattern, которая помещает поиск в список:
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.regex.Pattern;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
public class TestTreeModel extends DefaultTreeModel {
private static final long serialVersionUID = 1L;
public TestTreeModel() {
super(buildModel());
}
private static TreeNode buildModel() {
DefaultMutableTreeNode root = new DefaultMutableTreeNode("");
for (int i = 0; i < 3; i++) {
DefaultMutableTreeNode node = new DefaultMutableTreeNode("test" + i);
for (int j = 0; j < 3; j++) {
node.add(new DefaultMutableTreeNode("subtest" + j));
}
root.add(node);
}
return root;
}
@Override
public DefaultMutableTreeNode getRoot() {
return (DefaultMutableTreeNode) super.getRoot();
}
public List<TreeNode> findNodesMatchingPattern(Pattern pattern) {
List<TreeNode> matchingNodes = new ArrayList<>();
findNodesWithpattern(matchingNodes, getRoot(), pattern);
return matchingNodes;
}
private void findNodesWithpattern(List<TreeNode> matchingNodes, TreeNode node, Pattern pattern) {
if (pattern.matcher(node.toString()).find()) {
matchingNodes.add(node);
}
Enumeration<DefaultMutableTreeNode> children = node.children();
while (children.hasMoreElements()) {
findNodesWithpattern(matchingNodes, children.nextElement(), pattern);
}
}
}
Существует также модель строки, которая определяет один столбец с именем Value с хэш-кодом объекта (только для демонстрационных целей)
import org.netbeans.swing.outline.RowModel;
public class TestRowModel implements RowModel {
@Override
public Class getColumnClass(int column) {
return Integer.class;
}
@Override
public int getColumnCount() {
return 1;
}
@Override
public String getColumnName(int column) {
return "Value";
}
@Override
public Object getValueFor(Object node, int column) {
return node.hashCode();
}
@Override
public boolean isCellEditable(Object node, int column) {
return false;
}
@Override
public void setValueFor(Object node, int column, Object value) {
// do nothing for now
}
}
Визуализация, отвечающая за выделение желтым цветом.или оранжевый:
import java.awt.Color;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import org.netbeans.swing.outline.RenderDataProvider;
public class TestRenderData implements RenderDataProvider {
private String searchPattern;
private TreeNode currentMatch;
@Override
public java.awt.Color getBackground(Object o) {
return null;
}
@Override
public String getDisplayName(Object o) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) o;
if (searchPattern == null || searchPattern.length() == 0) {
return node.toString();
}
try {
Pattern pattern = Pattern.compile("(" + searchPattern + ")");
Matcher matcher = pattern.matcher(node.toString());
Color bgColor = Color.YELLOW;
if (matcher.find()) {
if (node == currentMatch) {
bgColor = Color.ORANGE;
}
}
String bgHexColor = Integer.toHexString(bgColor.getRGB() & 0xffffff);
String replacement = matcher.replaceAll("<span style=\"background-color: #" + bgHexColor + "\">$1</span>");
return "<html>" + replacement + "</html>";
} catch (PatternSyntaxException e) {
return node.toString();
}
}
@Override
public java.awt.Color getForeground(Object o) {
return null;
}
@Override
public javax.swing.Icon getIcon(Object o) {
return null;
}
@Override
public String getTooltipText(Object o) {
return null;
}
@Override
public boolean isHtmlDisplayName(Object o) {
return false;
}
public void setSearchPattern(String searchPattern) {
this.searchPattern = searchPattern;
}
public void setCurrentMatch(TreeNode currentMatch) {
this.currentMatch = currentMatch;
}
}
Основной класс строит схему с помощью быстрого фильтра (несоответствующие узлы отфильтровываются) и ключевого прослушивателя, который запускает поиск.Когда вводится клавиша Enter, он увеличивает счетчик (nthMatch), и текущее совпадение с индексом nthMatch подсвечивается в Orange средством визуализации.
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Enumeration;
import java.util.List;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuBar;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import org.netbeans.swing.etable.QuickFilter;
import org.netbeans.swing.outline.DefaultOutlineModel;
import org.netbeans.swing.outline.Outline;
import org.netbeans.swing.outline.OutlineModel;
public class MainFrame extends JFrame {
private static final long serialVersionUID = 1L;
public MainFrame() {
TestTreeModel treeMdl = new TestTreeModel();
OutlineModel mdl = DefaultOutlineModel.createOutlineModel(treeMdl, new TestRowModel(), true, "Test");
Outline outline = new Outline();
TestRenderData renderData = new TestRenderData();
outline.setRenderDataProvider(renderData);
outline.setRootVisible(false);
outline.setModel(mdl);
JScrollPane jScrollPane1 = new JScrollPane(outline);
jScrollPane1.setViewportView(outline);
JMenuBar mb = new JMenuBar();
JLabel searchPatternLabel = new JLabel(" Search : ");
mb.add(searchPatternLabel);
JTextField searchPatternTextField = new JTextField();
searchPatternLabel.setLabelFor(searchPatternTextField);
outline.setQuickFilter(0, new QuickFilter() {
@Override
public boolean accept(Object aValue) {
if (searchPatternTextField.getText() == null || searchPatternTextField.getText().length() == 0) {
return true;
}
if (aValue instanceof DefaultMutableTreeNode) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) aValue;
Enumeration<DefaultMutableTreeNode> children = node.children();
while (children.hasMoreElements()) {
DefaultMutableTreeNode child = (DefaultMutableTreeNode) children.nextElement();
if (accept(child)) {
return true;
}
}
try {
Pattern searchPattern = Pattern.compile(searchPatternTextField.getText());
return searchPattern.matcher(node.toString()).find();
} catch (PatternSyntaxException ex) {
return true;
}
}
return false;
}
});
mb.add(searchPatternTextField);
setJMenuBar(mb);
getContentPane().add(jScrollPane1);
pack();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
searchPatternTextField.addKeyListener(new KeyAdapter() {
private int nthMatch = 0;
@Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
if (e.isShiftDown()) {
nthMatch--;
} else {
nthMatch++;
}
}
try {
Pattern searchPattern = Pattern.compile(searchPatternTextField.getText());
List<TreeNode> matchingNodes = treeMdl.findNodesMatchingPattern(searchPattern);
if (matchingNodes.size() > 0) {
if (nthMatch >= matchingNodes.size()) {
nthMatch = 0;
}
if (nthMatch < 0) {
nthMatch = 0;
}
TreeNode matchingNode = matchingNodes.get(nthMatch);
renderData.setCurrentMatch(matchingNode);
TreePath matchingNodePath = new TreePath(((DefaultMutableTreeNode) matchingNode).getPath());
outline.expandPath(matchingNodePath);
outline.scrollRectToVisible(outline.getPathBounds(matchingNodePath));
renderData.setSearchPattern(searchPatternTextField.getText());
}
} catch (PatternSyntaxException ex) {
}
//outline.repaint();
treeMdl.nodeChanged(treeMdl.getRoot());
}
});
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new MainFrame();
}
});
}
}
В качестве базового сценария я неоднократно ищу "test"нажав Enter.Порядок результатов поиска портится при щелчке столбца для сортировки.В Outline есть 2 унаследованных метода convertRowIndexToModel и convertRowIndexToView, которые, похоже, выполняют отображение индексов, но я не знаю, как их использовать.