From 12b3181663525362cd1c87c8fd032a63494a2612 Mon Sep 17 00:00:00 2001 From: Hong Chen Date: Thu, 9 Jan 2025 15:39:45 -0600 Subject: [PATCH] Customized FindBar class that used to extend JXFindBar now extends JPanel to avoid the compatibility issue between Swing and newer JDK. Customized FindBar class that used to extend JXFindBar now extends JPanel to avoid the compatibility issue between Swing and newer JDK. --- .../java/trick/common/ui/panels/FindBar.java | 449 +++++++++++------- .../main/java/trick/sie/SieApplication.java | 2 +- .../simcontrol/SimControlApplication.java | 4 +- 3 files changed, 274 insertions(+), 181 deletions(-) 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 2ebab2d1d..28b68be98 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 @@ //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 - //======================================== - - - //======================================== - // 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(); - } - - /** - * 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(" ")); +public class FindBar extends JPanel { + + // ======================================== + // Public data + // ======================================== + + // ======================================== + // 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); - - 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); - } - }*/ - } -} + 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(); + } + } + +} \ 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 4d772792d..f0fd71fd4 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 @@ private void setupForViewingEnumeratedTypes(JComponent comp, Collection