blob: 032594cc16297038d8125c0594422c149950ab8f [file] [log] [blame]
/*
* 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();
}
}
}