| /* |
| * Copyright 2003-2007 the original author or authors. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package groovy.ui.text; |
| |
| import java.awt.BorderLayout; |
| import java.awt.Dimension; |
| import java.awt.FlowLayout; |
| import java.awt.GridLayout; |
| |
| import java.awt.event.ActionEvent; |
| import java.awt.event.FocusAdapter; |
| import java.awt.event.FocusEvent; |
| import java.awt.event.KeyAdapter; |
| import java.awt.event.KeyEvent; |
| import java.awt.event.TextEvent; |
| import java.awt.event.TextListener; |
| |
| import java.util.EventListener; |
| |
| import javax.swing.Action; |
| import javax.swing.AbstractAction; |
| import javax.swing.BoxLayout; |
| import javax.swing.JButton; |
| import javax.swing.JCheckBox; |
| import javax.swing.JComboBox; |
| import javax.swing.JDialog; |
| import javax.swing.JLabel; |
| import javax.swing.JPanel; |
| import javax.swing.JRootPane; |
| import javax.swing.KeyStroke; |
| |
| import javax.swing.event.EventListenerList; |
| |
| import javax.swing.text.AttributeSet; |
| import javax.swing.text.BadLocationException; |
| import javax.swing.text.Document; |
| import javax.swing.text.JTextComponent; |
| import javax.swing.text.Segment; |
| |
| /** |
| * |
| * @author Evan "Hippy" Slatis |
| */ |
| public final class FindReplaceUtility { |
| |
| public static final String FIND_ACTION_COMMAND = "Find"; |
| |
| public static final String REPLACE_ACTION_COMMAND = "Replace"; |
| |
| public static final String REPLACE_ALL_ACTION_COMMAND = "Replace All"; |
| |
| public static final String CLOSE_ACTION_COMMAND = "Close"; |
| |
| public static final Action FIND_ACTION = new FindAction(); |
| |
| private static final JDialog FIND_REPLACE_DIALOG = new JDialog(); |
| |
| private static final JPanel TEXT_FIELD_PANEL = new JPanel(new GridLayout(2, 1)); |
| |
| private static final JPanel ENTRY_PANEL = new JPanel(); |
| |
| private static final JPanel FIND_PANEL = new JPanel(); |
| private static final JLabel FIND_LABEL = new JLabel("Find What: "); |
| private static final JComboBox FIND_FIELD = new JComboBox(); |
| |
| private static final JPanel REPLACE_PANEL = new JPanel(); |
| private static final JLabel REPLACE_LABEL = new JLabel("Replace With:"); |
| private static final JComboBox REPLACE_FIELD = new JComboBox(); |
| |
| private static final JPanel BUTTON_PANEL = new JPanel(); |
| private static final JButton FIND_BUTTON = new JButton(); |
| private static final JButton REPLACE_BUTTON = new JButton(); |
| private static final JButton REPLACE_ALL_BUTTON = new JButton(); |
| private static final JButton CLOSE_BUTTON = new JButton(); |
| |
| private static final Action CLOSE_ACTION = new CloseAction(); |
| private static final Action REPLACE_ACTION = new ReplaceAction(); |
| |
| private static final JPanel CHECK_BOX_PANEL = new JPanel(new GridLayout(3, 1)); |
| private static final JCheckBox MATCH_CASE_CHECKBOX = new JCheckBox("Match Case "); |
| private static final JCheckBox IS_BACKWARDS_CHECKBOX = new JCheckBox("Search Backwards"); |
| private static final JCheckBox WRAP_SEARCH_CHECKBOX = new JCheckBox("Wrap Search "); |
| |
| private static JTextComponent textComponent; |
| private static AttributeSet attributeSet; |
| |
| private static int findReplaceCount; |
| private static String lastAction; |
| |
| private static final EventListenerList EVENT_LISTENER_LIST = new EventListenerList(); |
| |
| // the document segment |
| private static final Segment SEGMENT = new Segment(); |
| |
| private static final FocusAdapter TEXT_FOCUS_LISTENER = new FocusAdapter() { |
| public void focusGained(FocusEvent fe) { |
| textComponent = (JTextComponent)fe.getSource(); |
| attributeSet = |
| textComponent.getDocument().getDefaultRootElement().getAttributes(); |
| } |
| }; |
| |
| static { |
| FIND_REPLACE_DIALOG.setResizable(false); |
| FIND_REPLACE_DIALOG.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); |
| // is next line needed at all? |
| /* KeyStroke keyStroke = */ KeyStroke.getKeyStroke("enter"); |
| KeyAdapter keyAdapter = new KeyAdapter() { |
| public void keyTyped(KeyEvent ke) { |
| if (ke.getKeyChar() == KeyEvent.VK_ENTER) { |
| FIND_BUTTON.doClick(); |
| } |
| } |
| }; |
| FIND_PANEL.setLayout(new FlowLayout(FlowLayout.RIGHT)); |
| FIND_PANEL.add(FIND_LABEL); |
| FIND_PANEL.add(FIND_FIELD); |
| FIND_FIELD.addItem(""); |
| FIND_FIELD.setEditable(true); |
| FIND_FIELD.getEditor().getEditorComponent().addKeyListener(keyAdapter); |
| Dimension d = FIND_FIELD.getPreferredSize(); |
| d.width = 225; |
| FIND_FIELD.setPreferredSize(d); |
| |
| REPLACE_PANEL.add(REPLACE_LABEL); |
| REPLACE_PANEL.add(REPLACE_FIELD); |
| REPLACE_FIELD.setEditable(true); |
| REPLACE_FIELD.getEditor().getEditorComponent().addKeyListener(keyAdapter); |
| REPLACE_FIELD.setPreferredSize(d); |
| |
| TEXT_FIELD_PANEL.setLayout(new BoxLayout(TEXT_FIELD_PANEL, BoxLayout.Y_AXIS)); |
| TEXT_FIELD_PANEL.add(FIND_PANEL); |
| TEXT_FIELD_PANEL.add(REPLACE_PANEL); |
| |
| ENTRY_PANEL.add(TEXT_FIELD_PANEL); |
| FIND_REPLACE_DIALOG.getContentPane().add(ENTRY_PANEL, BorderLayout.WEST); |
| |
| CHECK_BOX_PANEL.add(MATCH_CASE_CHECKBOX); |
| |
| CHECK_BOX_PANEL.add(IS_BACKWARDS_CHECKBOX); |
| |
| CHECK_BOX_PANEL.add(WRAP_SEARCH_CHECKBOX); |
| |
| ENTRY_PANEL.add(CHECK_BOX_PANEL); |
| ENTRY_PANEL.setLayout(new BoxLayout(ENTRY_PANEL, BoxLayout.Y_AXIS)); |
| |
| REPLACE_ALL_BUTTON.setAction(new ReplaceAllAction()); |
| REPLACE_ALL_BUTTON.setHorizontalAlignment(JButton.CENTER); |
| d = REPLACE_ALL_BUTTON.getPreferredSize(); |
| |
| BUTTON_PANEL.setLayout(new BoxLayout(BUTTON_PANEL, BoxLayout.Y_AXIS)); |
| FIND_BUTTON.setAction(FIND_ACTION); |
| FIND_BUTTON.setPreferredSize(d); |
| FIND_BUTTON.setHorizontalAlignment(JButton.CENTER); |
| JPanel panel = new JPanel(); |
| panel.add(FIND_BUTTON); |
| BUTTON_PANEL.add(panel); |
| FIND_REPLACE_DIALOG.getRootPane().setDefaultButton(FIND_BUTTON); |
| |
| REPLACE_BUTTON.setAction(REPLACE_ACTION); |
| REPLACE_BUTTON.setPreferredSize(d); |
| REPLACE_BUTTON.setHorizontalAlignment(JButton.CENTER); |
| panel = new JPanel(); |
| panel.add(REPLACE_BUTTON); |
| BUTTON_PANEL.add(panel); |
| |
| panel = new JPanel(); |
| panel.add(REPLACE_ALL_BUTTON); |
| BUTTON_PANEL.add(panel); |
| |
| CLOSE_BUTTON.setAction(CLOSE_ACTION); |
| CLOSE_BUTTON.setPreferredSize(d); |
| CLOSE_BUTTON.setHorizontalAlignment(JButton.CENTER); |
| panel = new JPanel(); |
| panel.add(CLOSE_BUTTON); |
| BUTTON_PANEL.add(panel); |
| FIND_REPLACE_DIALOG.getContentPane().add(BUTTON_PANEL); |
| |
| KeyStroke stroke = (KeyStroke) CLOSE_ACTION.getValue(Action.ACCELERATOR_KEY); |
| JRootPane rPane = FIND_REPLACE_DIALOG.getRootPane(); |
| rPane.getInputMap(JButton.WHEN_IN_FOCUSED_WINDOW).put(stroke, "exit"); |
| rPane.getActionMap().put("exit", CLOSE_ACTION); |
| } |
| |
| // Singleton |
| private FindReplaceUtility() { |
| } |
| |
| public static void addTextListener(TextListener tl) { |
| EVENT_LISTENER_LIST.add(TextListener.class, tl); |
| } |
| |
| private static void fireTextEvent() { |
| EventListener[] lstrs = |
| EVENT_LISTENER_LIST.getListeners(TextListener.class); |
| if (lstrs != null && lstrs.length > 0) { |
| TextEvent te = |
| new TextEvent(FIND_REPLACE_DIALOG, TextEvent.TEXT_VALUE_CHANGED); |
| for (int i = 0; i < lstrs.length; i++) { |
| ((TextListener)lstrs[i]).textValueChanged(te); |
| } |
| } |
| } |
| |
| /** |
| * @return the last action |
| */ |
| public static String getLastAction() { |
| return lastAction; |
| } |
| |
| /** |
| * @return the replacement count |
| */ |
| public static int getReplacementCount() { |
| return findReplaceCount; |
| } |
| |
| /** |
| * @return the search text |
| */ |
| public static String getSearchText() { |
| return (String) FIND_FIELD.getSelectedItem(); |
| } |
| |
| /** |
| * @param textComponent the text component to listen to |
| */ |
| public static void registerTextComponent(JTextComponent textComponent) { |
| textComponent.addFocusListener(TEXT_FOCUS_LISTENER); |
| } |
| |
| public static void removeTextListener(TextListener tl) { |
| EVENT_LISTENER_LIST.remove(TextListener.class, tl); |
| } |
| |
| /** |
| * Find and select the next searchable matching text. |
| * |
| * @param reverse look forwards or backwards |
| * @param pos the starting index to start finding from |
| * @return the location of the next selected, or -1 if not found |
| */ |
| private static int findNext(boolean reverse, int pos) { |
| boolean backwards = IS_BACKWARDS_CHECKBOX.isSelected(); |
| backwards = backwards ? !reverse : reverse; |
| |
| String pattern = (String) FIND_FIELD.getSelectedItem(); |
| if (pattern != null && pattern.length() > 0) { |
| try { |
| Document doc = textComponent.getDocument(); |
| doc.getText(0, doc.getLength(), SEGMENT); |
| } |
| catch (Exception e) { |
| // should NEVER reach here |
| e.printStackTrace(); |
| } |
| |
| pos += textComponent.getSelectedText() == null ? |
| (backwards ? -1 : 1) : 0; |
| |
| char first = backwards ? |
| pattern.charAt(pattern.length() - 1) : pattern.charAt(0); |
| char oppFirst = Character.isUpperCase(first) ? |
| Character.toLowerCase(first) : Character.toUpperCase(first); |
| int start = pos; |
| boolean wrapped = WRAP_SEARCH_CHECKBOX.isSelected(); |
| int end = backwards ? 0 : SEGMENT.getEndIndex(); |
| pos += backwards ? -1 : 1; |
| |
| int length = textComponent.getDocument().getLength(); |
| if (pos > length) { |
| pos = wrapped ? 0 : length; |
| } |
| |
| boolean found = false; |
| while (!found && (backwards ? pos > end : pos < end)) { |
| found = !MATCH_CASE_CHECKBOX.isSelected() && SEGMENT.array[pos] == oppFirst; |
| found = found ? found : SEGMENT.array[pos] == first; |
| |
| if (found) { |
| pos += backwards ? -(pattern.length() - 1) : 0; |
| for (int i = 0; found && i < pattern.length(); i++) { |
| char c = pattern.charAt(i); |
| found = SEGMENT.array[pos + i] == c; |
| if (!MATCH_CASE_CHECKBOX.isSelected() && !found) { |
| c = Character.isUpperCase(c) ? |
| Character.toLowerCase(c) : |
| Character.toUpperCase(c); |
| found = SEGMENT.array[pos + i] == c; |
| } |
| } |
| } |
| |
| if (!found) { |
| pos += backwards ? -1 : 1; |
| |
| if (pos == end && wrapped) { |
| pos = backwards ? SEGMENT.getEndIndex() : 0; |
| end = start; |
| wrapped = false; |
| } |
| } |
| } |
| pos = found ? pos : -1; |
| } |
| |
| return pos; |
| } |
| |
| private static void setListStrings() { |
| Object findObject = FIND_FIELD.getSelectedItem(); |
| Object replaceObject = REPLACE_FIELD.isShowing() ? |
| (String) REPLACE_FIELD.getSelectedItem() : ""; |
| |
| if (findObject != null && replaceObject != null) { |
| boolean found = false; |
| for (int i = 0; !found && i < FIND_FIELD.getItemCount(); i++) { |
| found = FIND_FIELD.getItemAt(i).equals(findObject); |
| } |
| if (!found) { |
| FIND_FIELD.insertItemAt(findObject, 0); |
| if (FIND_FIELD.getItemCount() > 7) { |
| FIND_FIELD.removeItemAt(7); |
| } |
| } |
| |
| if (REPLACE_FIELD.isShowing()) { |
| found = false; |
| for (int i = 0; !found && i < REPLACE_FIELD.getItemCount(); i++) { |
| found = REPLACE_FIELD.getItemAt(i).equals(findObject); |
| } |
| if (!found) { |
| REPLACE_FIELD.insertItemAt(replaceObject, 0); |
| if (REPLACE_FIELD.getItemCount() > 7) { |
| REPLACE_FIELD.removeItemAt(7); |
| } |
| } |
| } |
| } |
| |
| } |
| |
| public static void showDialog() { |
| showDialog(false); |
| } |
| |
| /** |
| * @param isReplace show a replace dialog rather than a find dialog if true |
| */ |
| public static void showDialog(boolean isReplace) { |
| String title = isReplace ? REPLACE_ACTION_COMMAND : FIND_ACTION_COMMAND; |
| FIND_REPLACE_DIALOG.setTitle(title); |
| |
| String text = textComponent.getSelectedText(); |
| if (text == null) { |
| text = ""; |
| } |
| FIND_FIELD.getEditor().setItem(text); |
| FIND_FIELD.getEditor().selectAll(); |
| |
| REPLACE_PANEL.setVisible(isReplace); |
| REPLACE_ALL_BUTTON.setVisible(isReplace); |
| CLOSE_BUTTON.setVisible(isReplace); |
| |
| Action action = isReplace ? |
| REPLACE_ACTION : CLOSE_ACTION; |
| REPLACE_BUTTON.setAction(action); |
| |
| REPLACE_BUTTON.setPreferredSize(null); |
| Dimension d = isReplace ? |
| REPLACE_ALL_BUTTON.getPreferredSize() : |
| REPLACE_BUTTON.getPreferredSize(); |
| FIND_BUTTON.setPreferredSize(d); |
| REPLACE_BUTTON.setPreferredSize(d); |
| CLOSE_BUTTON.setPreferredSize(d); |
| |
| FIND_REPLACE_DIALOG.invalidate(); |
| FIND_REPLACE_DIALOG.repaint(); |
| FIND_REPLACE_DIALOG.pack(); |
| |
| java.awt.Frame[] frames = java.awt.Frame.getFrames(); |
| for (int i = 0; i < frames.length; i++) { |
| if (frames[i].isFocused()) { |
| FIND_REPLACE_DIALOG.setLocationRelativeTo(frames[i]); |
| } |
| } |
| |
| FIND_REPLACE_DIALOG.setVisible(true); |
| FIND_FIELD.requestFocusInWindow(); |
| } |
| |
| /** |
| * @param textComponent the text component to stop listening to |
| */ |
| public static void unregisterTextComponent(JTextComponent textComponent) { |
| textComponent.removeFocusListener(TEXT_FOCUS_LISTENER); |
| } |
| |
| private static class FindAction extends AbstractAction { |
| |
| public FindAction() { |
| putValue(Action.NAME, FIND_ACTION_COMMAND); |
| putValue(Action.ACTION_COMMAND_KEY, FIND_ACTION_COMMAND); |
| putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_F)); |
| } |
| |
| public void actionPerformed(ActionEvent ae) { |
| lastAction = FIND_ACTION_COMMAND; |
| findReplaceCount = 0; |
| |
| if (FIND_REPLACE_DIALOG.isVisible() && |
| FIND_REPLACE_DIALOG.getTitle().equals(FIND_ACTION_COMMAND)) { |
| } |
| |
| int pos = textComponent.getSelectedText() == null ? |
| textComponent.getCaretPosition() : |
| textComponent.getSelectionStart(); |
| |
| boolean reverse = (ae.getModifiers() & ActionEvent.SHIFT_MASK) != 0; |
| pos = findNext(reverse, pos); |
| |
| if (pos > -1) { |
| String pattern = (String) FIND_FIELD.getSelectedItem(); |
| textComponent.select(pos, pos + pattern.length()); |
| findReplaceCount = 1; |
| } |
| |
| setListStrings(); |
| |
| fireTextEvent(); |
| } |
| } |
| |
| private static class ReplaceAction extends AbstractAction { |
| |
| public ReplaceAction() { |
| putValue(Action.NAME, REPLACE_ACTION_COMMAND); |
| putValue(Action.ACTION_COMMAND_KEY, REPLACE_ACTION_COMMAND); |
| putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_R)); |
| } |
| |
| public void actionPerformed(ActionEvent ae) { |
| lastAction = ae.getActionCommand(); |
| findReplaceCount = 0; |
| |
| int pos = textComponent.getSelectedText() == null ? |
| textComponent.getCaretPosition() : |
| textComponent.getSelectionStart(); |
| |
| pos = findNext(false, pos - 1); |
| |
| if (pos > -1) { |
| String find = (String) FIND_FIELD.getSelectedItem(); |
| String replace = (String) REPLACE_FIELD.getSelectedItem(); |
| replace = replace == null ? "" : replace; |
| Document doc = textComponent.getDocument(); |
| try { |
| doc.remove(pos, find.length()); |
| doc.insertString(pos, replace, attributeSet); |
| |
| int last = pos; |
| pos = findNext(false, pos); |
| if (pos > -1) { |
| textComponent.select(pos, pos + find.length()); |
| } |
| else { |
| textComponent.setCaretPosition(last + replace.length()); |
| } |
| } |
| catch (BadLocationException ble) { |
| ble.printStackTrace(); |
| } |
| |
| findReplaceCount = 1; |
| } |
| setListStrings(); |
| |
| fireTextEvent(); |
| } |
| } |
| |
| private static class ReplaceAllAction extends AbstractAction { |
| |
| public ReplaceAllAction() { |
| putValue(Action.NAME, REPLACE_ALL_ACTION_COMMAND); |
| putValue(Action.ACTION_COMMAND_KEY, REPLACE_ALL_ACTION_COMMAND); |
| putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_A)); |
| } |
| |
| public void actionPerformed(ActionEvent ae) { |
| lastAction = ae.getActionCommand(); |
| findReplaceCount = 0; |
| |
| int last = textComponent.getSelectedText() == null ? |
| textComponent.getCaretPosition() : |
| textComponent.getSelectionStart(); |
| |
| int pos = findNext(false, last - 1); |
| |
| String find = (String) FIND_FIELD.getSelectedItem(); |
| String replace = (String) REPLACE_FIELD.getSelectedItem(); |
| replace = replace == null ? "" : replace; |
| while (pos > -1) { |
| Document doc = textComponent.getDocument(); |
| try { |
| doc.remove(pos, find.length()); |
| doc.insertString(pos, replace, attributeSet); |
| |
| last = pos; |
| pos = findNext(false, pos); |
| } |
| catch (BadLocationException ble) { |
| ble.printStackTrace(); |
| } |
| |
| findReplaceCount++; |
| } |
| |
| if (pos > -1) { |
| textComponent.select(pos, pos + find.length()); |
| } |
| else { |
| textComponent.setCaretPosition(last + replace.length()); |
| } |
| setListStrings(); |
| |
| fireTextEvent(); |
| } |
| } |
| |
| private static class CloseAction extends AbstractAction { |
| |
| public CloseAction() { |
| putValue(Action.NAME, CLOSE_ACTION_COMMAND); |
| putValue(Action.ACTION_COMMAND_KEY, CLOSE_ACTION_COMMAND); |
| putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_C)); |
| putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("ESCAPE")); |
| } |
| |
| public void actionPerformed(ActionEvent ae) { |
| FIND_REPLACE_DIALOG.dispose(); |
| } |
| } |
| } |