mirror of
https://github.com/nasa/trick.git
synced 2025-06-22 16:58:57 +00:00
Customized FindBar class that used to extend JXFindBar now extends JPanel to avoid the compatibility issue between Swing and newer JDK. (#1827)
Customized FindBar class that used to extend JXFindBar now extends JPanel to avoid the compatibility issue between Swing and newer JDK.
This commit is contained in:
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
//========================================
|
//========================================
|
||||||
// Package
|
// Package
|
||||||
//========================================
|
//========================================
|
||||||
@ -8,190 +7,282 @@ package trick.common.ui.panels;
|
|||||||
//Imports
|
//Imports
|
||||||
//========================================
|
//========================================
|
||||||
|
|
||||||
import java.awt.Color;
|
import javax.swing.*;
|
||||||
import java.awt.Component;
|
import javax.swing.event.DocumentEvent;
|
||||||
import java.awt.FlowLayout;
|
import javax.swing.event.DocumentListener;
|
||||||
|
import javax.swing.text.*;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
|
|
||||||
import javax.swing.Box;
|
import java.util.ArrayList;
|
||||||
import javax.swing.BoxLayout;
|
import java.util.List;
|
||||||
import javax.swing.JCheckBox;
|
import java.util.regex.MatchResult;
|
||||||
import javax.swing.JLabel;
|
import java.util.regex.Matcher;
|
||||||
import javax.swing.SwingConstants;
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.regex.PatternSyntaxException;
|
||||||
import org.jdesktop.swingx.AbstractPatternPanel;
|
|
||||||
import org.jdesktop.swingx.JXFindBar;
|
|
||||||
import org.jdesktop.swingx.JXFindPanel;
|
|
||||||
import org.jdesktop.swingx.search.Searchable;
|
|
||||||
|
|
||||||
|
|
||||||
|
import org.jdesktop.swingx.JXEditorPane;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link JXFindBar} that is for allowing users to input search text.
|
|
||||||
*
|
|
||||||
* @since Trick 10
|
* @since Trick 10
|
||||||
|
* A {@link JXFindBar} that allows users to input search text.
|
||||||
|
* This class extends JXFindBar, a SwingX component providing search bar functionality for a JXEditorPane.
|
||||||
|
*
|
||||||
|
* Notes for not extending JXFindBar:
|
||||||
|
* JXFindBar is a SwingX component extending JXFindPanel.
|
||||||
|
* JXFindPanel offers various search options, allowing users to input search text.
|
||||||
|
* When JXFindPanel performs a search, it calls the search method in the Searchable object.
|
||||||
|
* The Searchable search method is invoked when findNext or findPrevious is clicked.
|
||||||
|
* The JXEditorPane's Matcher calls toMatchResult() where the search text is used to find the match.
|
||||||
|
* The toMatchResult method is called in the Matcher class and creates a new Matcher, copying the state over.
|
||||||
|
* The toMatchResult method returns a {@link MatchResult} object.
|
||||||
|
* This works until JDK 9.
|
||||||
|
* JDK 9 introduces a new, non-public {@link java.util.regex.Matcher$ImmutableMatchResult} class that is the result of toMatchResult().
|
||||||
|
* Therefore, a Matcher can't be cloned and the state can't be copied over. The following exception would be seen:
|
||||||
|
* java.lang.ClassCastException: class java.util.regex.Matcher$ImmutableMatchResult cannot be cast to class java.util.regex.Matcher
|
||||||
|
* (java.util.regex.Matcher$ImmutableMatchResult and java.util.regex.Matcher are in module java.base of loader 'bootstrap')
|
||||||
|
*
|
||||||
|
* @since Trick 19
|
||||||
|
* This class no longer extends JXFindBar. It extends {@link JPanel} instead.
|
||||||
|
* FindBar is a custom JPanel that provides search functionality for a JXEditorPane.
|
||||||
|
* It allows users to search for text within the editor pane and navigate through the matches.
|
||||||
|
*
|
||||||
|
* <p>Usage example:</p>
|
||||||
|
* <pre>
|
||||||
|
* {@code
|
||||||
|
* JXEditorPane editorPane = new JXEditorPane();
|
||||||
|
* FindBar findBar = new FindBar(editorPane);
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public class FindBar extends JXFindBar implements ActionListener{
|
public class FindBar extends JPanel {
|
||||||
|
|
||||||
//========================================
|
// ========================================
|
||||||
// Public data
|
// Public data
|
||||||
//========================================
|
// ========================================
|
||||||
|
|
||||||
|
// ========================================
|
||||||
//========================================
|
|
||||||
// Protected data
|
// Protected data
|
||||||
//========================================
|
// ========================================
|
||||||
|
|
||||||
|
// ========================================
|
||||||
//========================================
|
|
||||||
// Private Data
|
// Private Data
|
||||||
//=======================================
|
// =======================================
|
||||||
|
|
||||||
private static final long serialVersionUID = 9092192049485321408L;
|
/**
|
||||||
|
* A text field for entering search queries.
|
||||||
|
*/
|
||||||
|
private JTextField searchField;
|
||||||
|
/**
|
||||||
|
* A button that, when pressed, triggers the action to find the next occurrence
|
||||||
|
* of the search term in the text or document.
|
||||||
|
*/
|
||||||
|
private JButton findNextButton;
|
||||||
|
/**
|
||||||
|
* JButton used to trigger the action of finding the previous occurrence
|
||||||
|
* in a search operation within the UI.
|
||||||
|
*/
|
||||||
|
private JButton findPreviousButton;
|
||||||
|
/**
|
||||||
|
* The JXEditorPane component used for displaying and editing text content.
|
||||||
|
*/
|
||||||
|
private JXEditorPane editorPane;
|
||||||
|
/**
|
||||||
|
* A list of integer positions.
|
||||||
|
* This list stores the positions of all find matches.
|
||||||
|
*/
|
||||||
|
private List<Integer> matchPosList;
|
||||||
|
/**
|
||||||
|
* A list of integers representing the lengths of each find match.
|
||||||
|
*/
|
||||||
|
private List<Integer> matchLenList;
|
||||||
|
/**
|
||||||
|
* The current index used for tracking the position within a list or array.
|
||||||
|
* This variable is typically used to keep track of the current item being
|
||||||
|
* processed or displayed.
|
||||||
|
*/
|
||||||
|
private int currentIndex;
|
||||||
|
|
||||||
/** The initial search text for this find bar */
|
// ========================================
|
||||||
private String initialSearchText = null;
|
|
||||||
|
|
||||||
/** By default, this would not have search options as JXFindPanel, true otherwise. */
|
|
||||||
private boolean hasOptions = false;
|
|
||||||
|
|
||||||
// TODO: add pattern support
|
|
||||||
//private JCheckBox anchorCheck;
|
|
||||||
|
|
||||||
//========================================
|
|
||||||
// Constructors
|
// Constructors
|
||||||
//========================================
|
// ========================================
|
||||||
/**
|
/**
|
||||||
* Default constructor.
|
* Constructs a new FindBar with the specified JXEditorPane.
|
||||||
*/
|
|
||||||
public FindBar() {
|
|
||||||
super();
|
|
||||||
setNotFoundForegroundColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The constructor that specifies total number of popup menus for the panel.
|
|
||||||
*
|
*
|
||||||
* @param searchable An instance of {@link Searchable} from a gui component which
|
* <p>
|
||||||
* this search bar is for.
|
* The FindBar consists of a JTextField for entering the search text, and two buttons
|
||||||
*/
|
* for navigating to the next and previous matches. It also highlights the current match
|
||||||
public FindBar(Searchable searchable) {
|
* in the editor pane.
|
||||||
super(searchable);
|
* </p>
|
||||||
setNotFoundForegroundColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
//========================================
|
|
||||||
// Set/Get methods
|
|
||||||
//========================================
|
|
||||||
/**
|
|
||||||
* Gets to see if case sensitive is checked.
|
|
||||||
* @return true or false
|
|
||||||
*/
|
|
||||||
public boolean isCaseSensitive() {
|
|
||||||
return getPatternModel().isCaseSensitive();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Sets whether to have the options as {@link JXFindPanel}.
|
|
||||||
* @param b true or false
|
|
||||||
*/
|
|
||||||
public void setOptions(boolean b) {
|
|
||||||
hasOptions = b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the searchField that is defined in parent class {@link AbstractPatternPanel}
|
|
||||||
* if there is initial search text is defined.
|
|
||||||
* @param searchText searchText
|
|
||||||
*/
|
|
||||||
public void updateSearchField(String searchText) {
|
|
||||||
initialSearchText = searchText;
|
|
||||||
if (searchField != null) {
|
|
||||||
searchField.setText(searchText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the current search text shown in searchField.
|
|
||||||
* @return the text
|
|
||||||
*/
|
|
||||||
public String getSearchText() {
|
|
||||||
if (searchField != null) {
|
|
||||||
return searchField.getText();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper method for changing the forground color when the text is not found.
|
|
||||||
* Since notFoundForegroundColor is proteced in {@link JXFindBar}, extending
|
|
||||||
* it is the only way to be able to change it.
|
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
|
* Features:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>Real-time search and highlight as the user types in the search field.</li>
|
||||||
|
* <li>Navigation buttons to find the next and previous matches.</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param editorPane The JXEditorPane in which the search will be performed.
|
||||||
*/
|
*/
|
||||||
private void setNotFoundForegroundColor() {
|
public FindBar(JXEditorPane editorPane) {
|
||||||
notFoundForegroundColor = Color.red;
|
this.editorPane = editorPane;
|
||||||
}
|
setLayout(new FlowLayout(FlowLayout.LEFT));
|
||||||
|
|
||||||
|
searchField = new JTextField(20);
|
||||||
|
findNextButton = new JButton("Find Next");
|
||||||
|
findPreviousButton = new JButton("Find Previous");
|
||||||
|
|
||||||
//========================================
|
// Add a DocumentListener to the search field to trigger search and highlight on
|
||||||
// Methods
|
// text change as real-time search.
|
||||||
//========================================
|
// The search field background color is set to red if no matches are found
|
||||||
|
// otherwise it is set to white.
|
||||||
|
searchField.getDocument().addDocumentListener(new DocumentListener() {
|
||||||
@Override
|
@Override
|
||||||
protected void build() {
|
public void insertUpdate(DocumentEvent e) {
|
||||||
if (hasOptions) {
|
searchAndHighlight();
|
||||||
buildBarWithOption();
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeUpdate(DocumentEvent e) {
|
||||||
|
searchAndHighlight();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void changedUpdate(DocumentEvent e) {
|
||||||
|
searchAndHighlight();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void searchAndHighlight() {
|
||||||
|
String searchText = searchField.getText();
|
||||||
|
findMatches(searchText);
|
||||||
|
highlightCurrentMatch();
|
||||||
|
if (matchPosList == null || matchPosList.isEmpty()) {
|
||||||
|
searchField.setBackground(Color.RED);
|
||||||
} else {
|
} else {
|
||||||
buildBar();
|
searchField.setBackground(Color.WHITE);
|
||||||
}
|
|
||||||
if (initialSearchText != null) {
|
|
||||||
updateSearchField(initialSearchText);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
private void buildBar() {
|
// Add an ActionListener to the search field to trigger search and highlight on Enter key press.
|
||||||
setLayout(new FlowLayout(SwingConstants.LEADING));
|
searchField.addActionListener(new ActionListener() {
|
||||||
add(searchLabel);
|
@Override
|
||||||
add(new JLabel(":"));
|
|
||||||
add(new JLabel(" "));
|
|
||||||
add(searchField);
|
|
||||||
add(findNext);
|
|
||||||
add(findPrevious);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void buildBarWithOption() {
|
|
||||||
//anchorCheck = new JCheckBox("Anchor");
|
|
||||||
//anchorCheck.addActionListener(this);
|
|
||||||
|
|
||||||
wrapCheck = new JCheckBox();
|
|
||||||
backCheck = new JCheckBox();
|
|
||||||
Box lBox = new Box(BoxLayout.LINE_AXIS);
|
|
||||||
//lBox.add(anchorCheck);
|
|
||||||
lBox.add(matchCheck);
|
|
||||||
lBox.add(wrapCheck);
|
|
||||||
lBox.add(backCheck);
|
|
||||||
lBox.setAlignmentY(Component.TOP_ALIGNMENT);
|
|
||||||
|
|
||||||
Box mBox = new Box(BoxLayout.LINE_AXIS);
|
|
||||||
mBox.add(searchLabel);
|
|
||||||
mBox.add(new JLabel(": "));
|
|
||||||
mBox.add(searchField);
|
|
||||||
mBox.add(findNext);
|
|
||||||
mBox.add(findPrevious);
|
|
||||||
mBox.setAlignmentY(Component.TOP_ALIGNMENT);
|
|
||||||
|
|
||||||
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
|
|
||||||
|
|
||||||
add(lBox);
|
|
||||||
add(mBox);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
/*if (e.getSource() == anchorCheck) {
|
String searchText = searchField.getText();
|
||||||
if (anchorCheck.isSelected()) {
|
findMatches(searchText);
|
||||||
getPatternModel().setRegexCreatorKey(PatternModel.REGEX_ANCHORED);
|
highlightCurrentMatch();
|
||||||
} else {
|
|
||||||
getPatternModel().setMatchRule(PatternModel.REGEX_MATCH_RULES);
|
|
||||||
}
|
}
|
||||||
}*/
|
});
|
||||||
|
|
||||||
|
// Add ActionListeners to the find next button.
|
||||||
|
findNextButton.addActionListener(new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
if (matchPosList != null && !matchPosList.isEmpty()) {
|
||||||
|
currentIndex = (currentIndex + 1) % matchPosList.size();
|
||||||
|
highlightCurrentMatch();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add ActionListeners to the find previous button.
|
||||||
|
findPreviousButton.addActionListener(new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
if (matchPosList != null && !matchPosList.isEmpty()) {
|
||||||
|
currentIndex = (currentIndex - 1 + matchPosList.size()) % matchPosList.size();
|
||||||
|
highlightCurrentMatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
add(new JLabel("Find:"));
|
||||||
|
add(searchField);
|
||||||
|
add(findNextButton);
|
||||||
|
add(findPreviousButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// Set/Get methods
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// Methods
|
||||||
|
// ========================================
|
||||||
|
/**
|
||||||
|
* Finds and highlights all matches of the given search text in the editor pane.
|
||||||
|
*
|
||||||
|
* @param searchText The text to search for within the editor pane.
|
||||||
|
* @throws PatternSyntaxException If the regular expression's syntax is invalid.
|
||||||
|
* @throws BadLocationException If the document's text cannot be retrieved.
|
||||||
|
*/
|
||||||
|
private void findMatches(String searchText) {
|
||||||
|
try {
|
||||||
|
Highlighter highlighter = editorPane.getHighlighter();
|
||||||
|
Highlighter.HighlightPainter painter = new DefaultHighlighter.DefaultHighlightPainter(Color.YELLOW);
|
||||||
|
highlighter.removeAllHighlights();
|
||||||
|
|
||||||
|
Document doc = editorPane.getDocument();
|
||||||
|
String text = doc.getText(0, doc.getLength());
|
||||||
|
matchPosList = new ArrayList<>();
|
||||||
|
matchLenList = new ArrayList<>();
|
||||||
|
Pattern pattern = Pattern.compile(searchText, Pattern.CASE_INSENSITIVE);
|
||||||
|
Matcher matcher = pattern.matcher(text);
|
||||||
|
|
||||||
|
// Find all matches of the search text in the document.
|
||||||
|
// Add the start to matchPosList and the length to matchLenList.
|
||||||
|
while (matcher.find()) {
|
||||||
|
matchPosList.add(matcher.start());
|
||||||
|
matchLenList.add(matcher.end() - matcher.start());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the current index to 0 and highlight the first match if available.
|
||||||
|
currentIndex = 0;
|
||||||
|
if (!matchPosList.isEmpty()) {
|
||||||
|
// Highlight the current match.
|
||||||
|
int pos = matchPosList.get(currentIndex);
|
||||||
|
highlighter.addHighlight(pos, pos + matchLenList.get(currentIndex), painter);
|
||||||
|
// Set the caret position to the start of the current match.
|
||||||
|
editorPane.setCaretPosition(pos);
|
||||||
|
}
|
||||||
|
} catch (PatternSyntaxException e) {
|
||||||
|
JOptionPane.showMessageDialog(this, "Invalid regular expression: " + e.getDescription(), "Error",
|
||||||
|
JOptionPane.ERROR_MESSAGE);
|
||||||
|
} catch (BadLocationException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highlights the current match in the editor pane.
|
||||||
|
*
|
||||||
|
* This method uses the Highlighter to highlight the text in the editor pane
|
||||||
|
* that matches the current search term. It removes any existing highlights
|
||||||
|
* before applying the new highlight. If there are positions available in the
|
||||||
|
* list, it highlights the text at the current index position and sets the
|
||||||
|
* caret position to the start of the highlighted text.
|
||||||
|
*
|
||||||
|
* @throws BadLocationException if the position is invalid in the document model
|
||||||
|
*/
|
||||||
|
private void highlightCurrentMatch() {
|
||||||
|
try {
|
||||||
|
Highlighter highlighter = editorPane.getHighlighter();
|
||||||
|
Highlighter.HighlightPainter painter = new DefaultHighlighter.DefaultHighlightPainter(Color.YELLOW);
|
||||||
|
highlighter.removeAllHighlights();
|
||||||
|
|
||||||
|
if (matchPosList != null && !matchPosList.isEmpty()) {
|
||||||
|
int pos = matchPosList.get(currentIndex);
|
||||||
|
// Highlight the current match.
|
||||||
|
highlighter.addHighlight(pos, pos + searchField.getText().length(), painter);
|
||||||
|
// Set the caret position to the start of the current match.
|
||||||
|
editorPane.setCaretPosition(pos);
|
||||||
|
}
|
||||||
|
} catch (BadLocationException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -444,7 +444,7 @@ public class SieApplication extends TrickApplication implements TreeSelectionLis
|
|||||||
contentPane.setEditable(false);
|
contentPane.setEditable(false);
|
||||||
contentPane.setContentType("text/html");
|
contentPane.setContentType("text/html");
|
||||||
|
|
||||||
FindBar findBar = new FindBar(contentPane.getSearchable());
|
FindBar findBar = new FindBar(contentPane);
|
||||||
contentPanel.add(findBar, BorderLayout.SOUTH);
|
contentPanel.add(findBar, BorderLayout.SOUTH);
|
||||||
|
|
||||||
// Add all the desired components to the comp
|
// Add all the desired components to the comp
|
||||||
|
@ -1207,7 +1207,9 @@ public class SimControlApplication extends TrickApplication implements PropertyC
|
|||||||
Font font = new Font("Monospaced", Font.PLAIN, curr_font_size);
|
Font font = new Font("Monospaced", Font.PLAIN, curr_font_size);
|
||||||
statusMsgPane.setFont(font);
|
statusMsgPane.setFont(font);
|
||||||
|
|
||||||
JPanel statusMsgPanel = UIUtils.createSearchableTitledPanel("Status Messages", statusMsgPane, new FindBar(statusMsgPane.getSearchable()));
|
// Create a customized search bar for the status message pane.
|
||||||
|
FindBar findBar = new FindBar(statusMsgPane);
|
||||||
|
JPanel statusMsgPanel = UIUtils.createSearchableTitledPanel("Status Messages", statusMsgPane, findBar);
|
||||||
return statusMsgPanel;
|
return statusMsgPanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user