diff --git a/trick_source/java/src/main/java/trick/common/ui/panels/FindBar.java b/trick_source/java/src/main/java/trick/common/ui/panels/FindBar.java
index 2ebab2d1..28b68be9 100644
--- a/trick_source/java/src/main/java/trick/common/ui/panels/FindBar.java
+++ b/trick_source/java/src/main/java/trick/common/ui/panels/FindBar.java
@@ -1,4 +1,3 @@
-
//========================================
// Package
//========================================
@@ -8,190 +7,282 @@ package trick.common.ui.panels;
//Imports
//========================================
-import java.awt.Color;
-import java.awt.Component;
-import java.awt.FlowLayout;
+import javax.swing.*;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.text.*;
+
+import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
-import javax.swing.Box;
-import javax.swing.BoxLayout;
-import javax.swing.JCheckBox;
-import javax.swing.JLabel;
-import javax.swing.SwingConstants;
-
-import org.jdesktop.swingx.AbstractPatternPanel;
-import org.jdesktop.swingx.JXFindBar;
-import org.jdesktop.swingx.JXFindPanel;
-import org.jdesktop.swingx.search.Searchable;
-
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.MatchResult;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+import org.jdesktop.swingx.JXEditorPane;
/**
- * A {@link JXFindBar} that is for allowing users to input search text.
- *
* @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.
+ *
+ *
Usage example:
+ *
+ * {@code
+ * JXEditorPane editorPane = new JXEditorPane();
+ * FindBar findBar = new FindBar(editorPane);
+ * }
+ *
*/
-public class FindBar extends JXFindBar implements ActionListener{
-
- //========================================
- // Public data
- //========================================
-
-
- //========================================
- // Protected data
- //========================================
-
+public class FindBar extends JPanel {
- //========================================
- // Private Data
- //=======================================
-
- private static final long serialVersionUID = 9092192049485321408L;
-
- /** 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
- //========================================
- /**
- * Default constructor.
- */
- public FindBar() {
- super();
- setNotFoundForegroundColor();
- }
+ // ========================================
+ // Public data
+ // ========================================
- /**
- * The constructor that specifies total number of popup menus for the panel.
- *
- * @param searchable An instance of {@link Searchable} from a gui component which
- * this search bar is for.
- */
- public FindBar(Searchable searchable) {
- super(searchable);
- 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.
- *
- */
- private void setNotFoundForegroundColor() {
- notFoundForegroundColor = Color.red;
- }
-
-
- //========================================
- // Methods
- //========================================
- @Override
- protected void build() {
- if (hasOptions) {
- buildBarWithOption();
- } else {
- buildBar();
- }
- if (initialSearchText != null) {
- updateSearchField(initialSearchText);
- }
- }
-
- private void buildBar() {
- setLayout(new FlowLayout(SwingConstants.LEADING));
- add(searchLabel);
- add(new JLabel(":"));
- add(new JLabel(" "));
+ // ========================================
+ // Protected data
+ // ========================================
+
+ // ========================================
+ // Private Data
+ // =======================================
+
+ /**
+ * 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 matchPosList;
+ /**
+ * A list of integers representing the lengths of each find match.
+ */
+ private List 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;
+
+ // ========================================
+ // Constructors
+ // ========================================
+ /**
+ * Constructs a new FindBar with the specified JXEditorPane.
+ *
+ *
+ * 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
+ * in the editor pane.
+ *
+ *
+ *
+ * Features:
+ *
+ *
+ * - Real-time search and highlight as the user types in the search field.
+ * - Navigation buttons to find the next and previous matches.
+ *
+ *
+ * @param editorPane The JXEditorPane in which the search will be performed.
+ */
+ public FindBar(JXEditorPane editorPane) {
+ 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
+ // 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
+ public void insertUpdate(DocumentEvent e) {
+ searchAndHighlight();
+ }
+
+ @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 {
+ searchField.setBackground(Color.WHITE);
+ }
+ }
+ });
+
+ // Add an ActionListener to the search field to trigger search and highlight on Enter key press.
+ searchField.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ String searchText = searchField.getText();
+ findMatches(searchText);
+ highlightCurrentMatch();
+ }
+ });
+
+ // 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(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);
+ add(findNextButton);
+ add(findPreviousButton);
+ }
- setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
-
- add(lBox);
- add(mBox);
- }
-
- public void actionPerformed(ActionEvent e) {
- /*if (e.getSource() == anchorCheck) {
- if (anchorCheck.isSelected()) {
- getPatternModel().setRegexCreatorKey(PatternModel.REGEX_ANCHORED);
- } else {
- getPatternModel().setMatchRule(PatternModel.REGEX_MATCH_RULES);
- }
- }*/
- }
-}
+ // ========================================
+ // 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();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/trick_source/java/src/main/java/trick/sie/SieApplication.java b/trick_source/java/src/main/java/trick/sie/SieApplication.java
index 4d772792..f0fd71fd 100644
--- a/trick_source/java/src/main/java/trick/sie/SieApplication.java
+++ b/trick_source/java/src/main/java/trick/sie/SieApplication.java
@@ -444,7 +444,7 @@ public class SieApplication extends TrickApplication implements TreeSelectionLis
contentPane.setEditable(false);
contentPane.setContentType("text/html");
- FindBar findBar = new FindBar(contentPane.getSearchable());
+ FindBar findBar = new FindBar(contentPane);
contentPanel.add(findBar, BorderLayout.SOUTH);
// Add all the desired components to the comp
diff --git a/trick_source/java/src/main/java/trick/simcontrol/SimControlApplication.java b/trick_source/java/src/main/java/trick/simcontrol/SimControlApplication.java
index b6b0ae9c..8a77fd74 100644
--- a/trick_source/java/src/main/java/trick/simcontrol/SimControlApplication.java
+++ b/trick_source/java/src/main/java/trick/simcontrol/SimControlApplication.java
@@ -1207,7 +1207,9 @@ public class SimControlApplication extends TrickApplication implements PropertyC
Font font = new Font("Monospaced", Font.PLAIN, curr_font_size);
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;
}