find/replace fixes from Peter N Lewis, new Quaqua to fix checkboxes in popups, focus fix

This commit is contained in:
benfry
2011-04-16 16:11:49 +00:00
parent d6a8aa9736
commit a2f56e0692
4 changed files with 336 additions and 214 deletions

View File

@@ -716,14 +716,34 @@ public abstract class Editor extends JFrame implements RunnerListener {
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (find != null) {
//find.find(true);
//FindReplace find = new FindReplace(Editor.this); //.show();
find.find(true);
find.findNext();
}
}
});
menu.add(item);
item = Base.newJMenuItemShift("Find Previous", 'G');
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (find != null) {
find.findPrevious();
}
}
});
menu.add(item);
// For Arduino and Mac, this should be command-E, but that currently conflicts with Export Applet
item = new JMenuItem("Use Selection for Find");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (find == null) {
find = new FindReplace(Editor.this);
}
find.setFindText( getSelectedText() );
}
});
menu.add(item);
return menu;
}

View File

@@ -3,7 +3,7 @@
/*
Part of the Processing project - http://processing.org
Copyright (c) 2004-10 Ben Fry and Casey Reas
Copyright (c) 2004-11 Ben Fry and Casey Reas
Copyright (c) 2001-04 Massachusetts Institute of Technology
This program is free software; you can redistribute it and/or modify
@@ -32,8 +32,10 @@ import javax.swing.*;
*/
public class FindReplace extends JFrame implements ActionListener {
static final int BIG = 13;
static final int EDGE = Base.isMacOS() ? 20 : 13;
static final int SMALL = 6;
// 12 is correct for Mac, other numbers may be required for other platforms
static final int BUTTON_GAP = 12;
Editor editor;
@@ -45,13 +47,14 @@ public class FindReplace extends JFrame implements ActionListener {
JButton replaceButton;
JButton replaceAllButton;
JButton replaceFindButton;
JButton previousButton;
JButton findButton;
JCheckBox ignoreCaseBox;
static boolean ignoreCase = true;
/// true when there's something selected in the editor
boolean found;
JCheckBox wrapAroundBox;
static boolean wrapAround = true;
public FindReplace(Editor editor) {
@@ -63,20 +66,63 @@ public class FindReplace extends JFrame implements ActionListener {
pain.setLayout(null);
JLabel findLabel = new JLabel("Find:");
Dimension d0 = findLabel.getPreferredSize();
JLabel replaceLabel = new JLabel("Replace with:");
Dimension d1 = replaceLabel.getPreferredSize();
Dimension labelDimension = replaceLabel.getPreferredSize();
pain.add(findLabel);
pain.add(replaceLabel);
pain.add(findField = new JTextField(20));
pain.add(replaceField = new JTextField(20));
Dimension d2 = findField.getPreferredSize();
pain.add(findField = new JTextField());
pain.add(replaceField = new JTextField());
int fieldHeight = findField.getPreferredSize().height;
if (findString != null) findField.setText(findString);
if (replaceString != null) replaceField.setText(replaceString);
ignoreCaseBox = new JCheckBox("Ignore Case");
ignoreCaseBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
ignoreCase = ignoreCaseBox.isSelected();
}
});
ignoreCaseBox.setSelected(ignoreCase);
pain.add(ignoreCaseBox);
wrapAroundBox = new JCheckBox("Wrap Around");
wrapAroundBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
wrapAround = wrapAroundBox.isSelected();
}
});
wrapAroundBox.setSelected(wrapAround);
pain.add(wrapAroundBox);
JPanel buttons = new JPanel();
buttons.setLayout(new FlowLayout(FlowLayout.CENTER,BUTTON_GAP, 0));
// ordering is different on mac versus pc
if (Base.isMacOS()) {
buttons.add(replaceAllButton = new JButton("Replace All"));
buttons.add(replaceButton = new JButton("Replace"));
buttons.add(replaceFindButton = new JButton("Replace & Find"));
buttons.add(previousButton = new JButton("Previous"));
buttons.add(findButton = new JButton("Find"));
} else {
buttons.add(findButton = new JButton("Find"));
buttons.add(previousButton = new JButton("Previous")); // is this the right position for non-Mac?
buttons.add(replaceFindButton = new JButton("Replace & Find"));
buttons.add(replaceButton = new JButton("Replace"));
buttons.add(replaceAllButton = new JButton("Replace All"));
}
pain.add(buttons);
// to fix ugliness.. normally macosx java 1.3 puts an
// ugly white border around this object, so turn it off.
if (Base.isMacOS()) {
buttons.setBorder(null);
}
/*
findField.addFocusListener(new FocusListener() {
public void focusGained(FocusEvent e) {
@@ -92,91 +138,74 @@ public class FindReplace extends JFrame implements ActionListener {
});
*/
// +1 since it's better to tend downwards
int yoff = (1 + d2.height - d1.height) / 2;
Dimension buttonsDimension = buttons.getPreferredSize();
int visibleButtonWidth = buttonsDimension.width - 2 * BUTTON_GAP;
int fieldWidth = visibleButtonWidth - (labelDimension.width + SMALL);
findLabel.setBounds(BIG + (d1.width-d0.width) + yoff, BIG,
d1.width, d1.height);
replaceLabel.setBounds(BIG, BIG + d2.height + SMALL + yoff,
d1.width, d1.height);
// +1 since it's better to tend downwards
int yoff = (1 + fieldHeight - labelDimension.height) / 2;
ignoreCaseBox = new JCheckBox("Ignore Case");
ignoreCaseBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
ignoreCase = ignoreCaseBox.isSelected();
}
});
ignoreCaseBox.setSelected(ignoreCase);
pain.add(ignoreCaseBox);
int ypos = EDGE;
//
int labelWidth = findLabel.getPreferredSize().width;
findLabel.setBounds(EDGE + (labelDimension.width-labelWidth), ypos + yoff, // + yoff was added to the wrong field
labelWidth, labelDimension.height);
findField.setBounds(EDGE + labelDimension.width + SMALL, ypos,
fieldWidth, fieldHeight);
JPanel buttons = new JPanel();
buttons.setLayout(new FlowLayout());
ypos += fieldHeight + SMALL;
// ordering is different on mac versus pc
if (Base.isMacOS()) {
buttons.add(replaceAllButton = new JButton("Replace All"));
buttons.add(replaceButton = new JButton("Replace"));
buttons.add(replaceFindButton = new JButton("Replace & Find"));
buttons.add(findButton = new JButton("Find"));
labelWidth = replaceLabel.getPreferredSize().width;
replaceLabel.setBounds(EDGE + (labelDimension.width-labelWidth), ypos + yoff,
labelWidth, labelDimension.height);
replaceField.setBounds(EDGE + labelDimension.width + SMALL, ypos,
fieldWidth, fieldHeight);
} else {
buttons.add(findButton = new JButton("Find"));
buttons.add(replaceFindButton = new JButton("Replace & Find"));
buttons.add(replaceButton = new JButton("Replace"));
buttons.add(replaceAllButton = new JButton("Replace All"));
}
pain.add(buttons);
ypos += fieldHeight + SMALL;
// to fix ugliness.. normally macosx java 1.3 puts an
// ugly white border around this object, so turn it off.
if (Base.isMacOS()) {
buttons.setBorder(null);
}
ignoreCaseBox.setBounds(EDGE + labelDimension.width + SMALL,
ypos,
(fieldWidth-SMALL)/2, fieldHeight);
Dimension d3 = buttons.getPreferredSize();
//buttons.setBounds(BIG, BIG + d2.height*2 + SMALL + BIG,
buttons.setBounds(BIG, BIG + d2.height*3 + SMALL*2 + BIG,
d3.width, d3.height);
wrapAroundBox.setBounds(EDGE + labelDimension.width + SMALL + (fieldWidth-SMALL)/2 + SMALL,
ypos,
(fieldWidth-SMALL)/2, fieldHeight);
//
ypos += fieldHeight + SMALL;
findField.setBounds(BIG + d1.width + SMALL, BIG,
d3.width - (d1.width + SMALL), d2.height);
replaceField.setBounds(BIG + d1.width + SMALL, BIG + d2.height + SMALL,
d3.width - (d1.width + SMALL), d2.height);
buttons.setBounds(EDGE-BUTTON_GAP, ypos,
buttonsDimension.width, buttonsDimension.height);
ignoreCaseBox.setBounds(BIG + d1.width + SMALL,
BIG + d2.height*2 + SMALL*2,
d3.width, d2.height);
ypos += buttonsDimension.height + EDGE;
//
// Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
int wide = visibleButtonWidth + EDGE*2;
int high = ypos; // butt.y + butt.height + EDGE*2 + SMALL;
pack();
Insets insets = getInsets();
// System.out.println("Insets = " + insets);
setSize(wide + insets.left + insets.right,high + insets.top + insets.bottom);
setLocationRelativeTo( null ); // center
// setBounds((screen.width - wide) / 2, (screen.height - high) / 2, wide, high);
replaceButton.addActionListener(this);
replaceAllButton.addActionListener(this);
replaceFindButton.addActionListener(this);
findButton.addActionListener(this);
previousButton.addActionListener(this);
// you mustn't replace what you haven't found, my son
replaceButton.setEnabled(false);
replaceFindButton.setEnabled(false);
// so that typing will go straight to this field
//findField.requestFocus();
// semantics of replace are "replace the current selection with the replace field"
// so whether we have found before or not is irrelevent
// replaceButton.setEnabled(false);
// replaceFindButton.setEnabled(false);
// make the find button the blinky default
getRootPane().setDefaultButton(findButton);
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
int wide = d3.width + BIG*2;
Rectangle butt = buttons.getBounds(); // how big is your butt?
int high = butt.y + butt.height + BIG*2 + SMALL;
setBounds((screen.width - wide) / 2,
(screen.height - high) / 2, wide, high);
setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
@@ -227,11 +256,13 @@ public class FindReplace extends JFrame implements ActionListener {
Object source = e.getSource();
if (source == findButton) {
find(true);
findNext();
} else if (source == previousButton) {
findPrevious();
} else if (source == replaceFindButton) {
replace();
find(true);
replaceAndFindNext();
} else if (source == replaceButton) {
replace();
@@ -242,50 +273,52 @@ public class FindReplace extends JFrame implements ActionListener {
}
// look for the next instance of the find string
// to be found later than the current caret selection
// look for the next instance of the find string to be found
// once found, select it (and go to that line)
public void find(boolean wrap) {
// in case search len is zero,
// otherwise replace all will go into an infinite loop
found = false;
private boolean find(boolean wrap, boolean backwards) {
String search = findField.getText();
//System.out.println("finding for " + search + " " + findString);
// this will catch "find next" being called when no search yet
if (search.length() == 0) return;
if (search.length() == 0) return false;
String text = editor.getText();
if (ignoreCase) {
search = search.toLowerCase();
search = search.toLowerCase();
text = text.toLowerCase();
}
//int selectionStart = editor.textarea.getSelectionStart();
int selectionEnd = editor.getSelectionStop();
int nextIndex;
if (!backwards) {
//int selectionStart = editor.textarea.getSelectionStart();
int selectionEnd = editor.getSelectionStop();
int nextIndex = text.indexOf(search, selectionEnd);
if (nextIndex == -1) {
if (wrap) {
nextIndex = text.indexOf(search, selectionEnd);
if (wrap && nextIndex == -1) {
// if wrapping, a second chance is ok, start from beginning
nextIndex = text.indexOf(search, 0);
}
} else {
//int selectionStart = editor.textarea.getSelectionStart();
int selectionStart = editor.getSelectionStart()-1;
if (nextIndex == -1) {
found = false;
replaceButton.setEnabled(false);
replaceFindButton.setEnabled(false);
//Toolkit.getDefaultToolkit().beep();
return;
if ( selectionStart >= 0 ) {
nextIndex = text.lastIndexOf(search, selectionStart);
} else {
nextIndex = -1;
}
if (wrap && nextIndex == -1) {
// if wrapping, a second chance is ok, start from the end
nextIndex = text.lastIndexOf(search);
}
}
found = true;
replaceButton.setEnabled(true);
replaceFindButton.setEnabled(true);
editor.setSelection(nextIndex, nextIndex + search.length());
if (nextIndex != -1) {
editor.setSelection(nextIndex, nextIndex + search.length());
} else {
//Toolkit.getDefaultToolkit().beep();
}
return nextIndex != -1;
}
@@ -294,26 +327,18 @@ public class FindReplace extends JFrame implements ActionListener {
* replacement text field.
*/
public void replace() {
if (!found) return; // don't replace if nothing found
// check to see if the document has wrapped around
// otherwise this will cause an infinite loop
String sel = editor.getSelectedText();
if (sel.equals(replaceField.getText())) {
found = false;
replaceButton.setEnabled(false);
replaceFindButton.setEnabled(false);
return;
}
editor.setSelectedText(replaceField.getText());
//editor.setSketchModified(true);
//editor.sketch.setCurrentModified(true);
editor.getSketch().setModified(true); // TODO is this necessary?
}
// don't allow a double replace
replaceButton.setEnabled(false);
replaceFindButton.setEnabled(false);
/**
* Replace the current selection with whatever's in the
* replacement text field, and then find the next match
*/
public void replaceAndFindNext() {
replace();
findNext();
}
@@ -325,9 +350,37 @@ public class FindReplace extends JFrame implements ActionListener {
// move to the beginning
editor.setSelection(0, 0);
do {
find(false);
replace();
} while (found);
boolean foundAtLeastOne = false;
while ( true ) {
if ( find(false,false) ) {
foundAtLeastOne = true;
replace();
} else {
break;
}
}
if ( !foundAtLeastOne ) {
Toolkit.getDefaultToolkit().beep();
}
}
public void setFindText( String t ) {
findField.setText( t );
findString = t;
}
public void findNext() {
if ( !find( wrapAround, false ) ) {
Toolkit.getDefaultToolkit().beep();
}
}
public void findPrevious() {
if ( !find( wrapAround, true ) ) {
Toolkit.getDefaultToolkit().beep();
}
}
}

View File

@@ -833,6 +833,23 @@ public class JEditTextArea extends JComponent
return lineElement.getEndOffset();
}
/**
* Returns the start offset of the line after this line, or the end of
* this line if there is no next line.
* @param line The line
* @return The end offset of the specified line, or -1 if the line is
* invalid.
*/
public int getLineSelectionStopOffset(int line)
{
Element lineElement = document.getDefaultRootElement()
.getElement(line);
if(lineElement == null)
return -1;
else
return Math.min(lineElement.getEndOffset(),getDocumentLength());
}
/**
* Returns the length of the specified line.
* @param line The line
@@ -1143,7 +1160,7 @@ public class JEditTextArea extends JComponent
{
throw new IllegalArgumentException("Bounds out of"
+ " range: " + newStart + "," +
newEnd);
newEnd + " [" + getDocumentLength() + "]");
}
// If the new position is the same as the old, we don't
@@ -1200,6 +1217,64 @@ public class JEditTextArea extends JComponent
// getLineOfOffset(end));
}
}
private enum CharacterKinds {
Word,
Whitespace,
Other
}
private CharacterKinds CharacterKind( char ch, String noWordSep )
{
if ( Character.isLetterOrDigit(ch) || ch=='_' || noWordSep.indexOf(ch) != -1 )
return CharacterKinds.Word;
else if ( Character.isWhitespace(ch) )
return CharacterKinds.Whitespace;
else
return CharacterKinds.Other;
}
protected void setNewSelectionWord( int line, int offset )
{
if (getLineLength(line) == 0) {
newSelectionStart = getLineStartOffset(line);
newSelectionEnd = newSelectionStart;
return;
}
String noWordSep = (String)document.getProperty("noWordSep");
if(noWordSep == null)
noWordSep = "";
String lineText = getLineText(line);
int wordStart = 0;
int wordEnd = lineText.length();
char ch = lineText.charAt(Math.max(0,offset - 1));
CharacterKinds thisWord = CharacterKind(ch,noWordSep);
for(int i = offset - 1; i >= 0; i--) {
ch = lineText.charAt(i);
if(CharacterKind(ch,noWordSep) != thisWord) {
wordStart = i + 1;
break;
}
}
for(int i = offset; i < lineText.length(); i++) {
ch = lineText.charAt(i);
if(CharacterKind(ch,noWordSep) != thisWord) {
wordEnd = i;
break;
}
}
int lineStart = getLineStartOffset(line);
newSelectionStart = lineStart + wordStart;
newSelectionEnd = lineStart + wordEnd;
}
/**
@@ -1833,6 +1908,14 @@ public class JEditTextArea extends JComponent
protected int selectionEndLine;
protected boolean biasLeft;
protected int newSelectionStart; // hack to get around lack of multiple returns in Java
protected int newSelectionEnd;
protected boolean selectWord;
protected boolean selectLine;
protected int selectionAncorStart;
protected int selectionAncorEnd;
protected int bracketPosition;
protected int bracketLine;
@@ -2171,9 +2254,26 @@ public class JEditTextArea extends JComponent
{
if (popup != null && popup.isVisible()) return;
if ( !selectWord && !selectLine ) {
setSelectionRectangular((evt.getModifiers()
& InputEvent.CTRL_MASK) != 0);
select(getMarkPosition(),xyToOffset(evt.getX(),evt.getY()));
} else {
int line = yToLine(evt.getY());
if ( selectWord ) {
setNewSelectionWord( line, xToOffset(line,evt.getX()) );
} else {
newSelectionStart = getLineStartOffset(line);
newSelectionEnd = getLineSelectionStopOffset(line);
}
if ( newSelectionStart < selectionAncorStart ) {
select(newSelectionStart,selectionAncorEnd);
} else if ( newSelectionEnd > selectionAncorEnd ) {
select(selectionAncorStart,newSelectionEnd);
} else {
select(newSelectionStart,newSelectionEnd);
}
}
}
public void mouseMoved(MouseEvent evt) {}
@@ -2202,7 +2302,7 @@ public class JEditTextArea extends JComponent
{
public void mousePressed(MouseEvent evt)
{
try {
// try {
// requestFocus();
// // Focus events not fired sometimes?
// setCaretVisible(true);
@@ -2214,7 +2314,10 @@ public class JEditTextArea extends JComponent
// the problem, though it's not clear why the wrong Document data was
// being using regardless of the focusedComponent.
// if (focusedComponent != JEditTextArea.this) return;
if (!hasFocus()) return;
if (!hasFocus()) {
requestFocusInWindow();
return;
}
// isPopupTrigger wasn't working for danh on windows
boolean trigger = (evt.getModifiers() & InputEvent.BUTTON3_MASK) != 0;
@@ -2231,6 +2334,9 @@ public class JEditTextArea extends JComponent
int offset = xToOffset(line,evt.getX());
int dot = getLineStartOffset(line) + offset;
selectLine = false;
selectWord = false;
switch(evt.getClickCount()) {
case 1:
@@ -2251,11 +2357,11 @@ public class JEditTextArea extends JComponent
doTripleClick(evt,line,offset,dot);
break;
}
} catch (ArrayIndexOutOfBoundsException aioobe) {
aioobe.printStackTrace();
int line = yToLine(evt.getY());
System.out.println("line is " + line + ", line count is " + getLineCount());
}
// } catch (ArrayIndexOutOfBoundsException aioobe) {
// aioobe.printStackTrace();
// int line = yToLine(evt.getY());
// System.out.println("line is " + line + ", line count is " + getLineCount());
// }
}
@@ -2294,74 +2400,11 @@ public class JEditTextArea extends JComponent
bl.printStackTrace();
}
String noWordSep = (String)document.getProperty("noWordSep");
if(noWordSep == null)
noWordSep = "";
// Ok, it's not a bracket... select the word
String lineText = getLineText(line);
int wordStart = 0;
int wordEnd = lineText.length();
char ch = lineText.charAt(Math.max(0,offset - 1));
// special case for whitespace (fry 0122, bug #348)
// this is really nasty.. turns out that double-clicking any non-letter
// or digit char gets lumped together.. sooo, this quickly gets messy,
// because really it needs to check whether the chars are of the same
// type.. so a double space or double - might be grouped together,
// but what about a +=1? do + and - get grouped but not the 1? blech,
// coming back to this later. it's not a difficult fix, just a
// time-consuming one to track down all the proper cases.
/*
if (ch == ' ') {
//System.out.println("yeehaa");
for(int i = offset - 1; i >= 0; i--) {
if (lineText.charAt(i) == ' ') {
wordStart = i;
} else {
break;
}
}
for(int i = offset; i < lineText.length(); i++) {
if (lineText.charAt(i) == ' ') {
wordEnd = i + 1;
} else {
break;
}
}
} else {
*/
// If the user clicked on a non-letter char,
// we select the surrounding non-letters
boolean selectNoLetter = (!Character.isLetterOrDigit(ch)
&& noWordSep.indexOf(ch) == -1);
for(int i = offset - 1; i >= 0; i--) {
ch = lineText.charAt(i);
if (selectNoLetter ^ (!Character.isLetterOrDigit(ch) &&
noWordSep.indexOf(ch) == -1)) {
wordStart = i + 1;
break;
}
}
for(int i = offset; i < lineText.length(); i++) {
ch = lineText.charAt(i);
if(selectNoLetter ^ (!Character.isLetterOrDigit(ch) &&
noWordSep.indexOf(ch) == -1)) {
wordEnd = i;
break;
}
}
//}
int lineStart = getLineStartOffset(line);
select(lineStart + wordStart,lineStart + wordEnd);
setNewSelectionWord( line, offset );
select(newSelectionStart,newSelectionEnd);
selectWord = true;
selectionAncorStart = selectionStart;
selectionAncorEnd = selectionEnd;
/*
String lineText = getLineText(line);
@@ -2377,7 +2420,10 @@ public class JEditTextArea extends JComponent
private void doTripleClick(MouseEvent evt, int line,
int offset, int dot)
{
select(getLineStartOffset(line),getLineStopOffset(line)-1);
selectLine = true;
select(getLineStartOffset(line),getLineSelectionStopOffset(line));
selectionAncorStart = selectionStart;
selectionAncorEnd = selectionEnd;
}
}

View File

@@ -1,27 +1,23 @@
0196
_ checkbox in mode menu smashing into things
_ remove opengl2 for 1.5 and examples
_ colors for 2.0
_ nurbs or other arch stuff for 2.0?
_ changing number of screens between run causes things to show up off-screen
_ so when running, check to make sure that things are out of the area
X upgrade to Quaqua 7.3.4 on OS X
X fixes checkbox in mode menu smashing into things
C build a new version of the reference
_ help casey re-export all applets for the site
_ remove .java files, use one central loading.gif and core.jar?
_ use a custom applet.html template for the export
_ the tool could copy this into the folder just before exporting
_ build a new version of the reference
_ changing number of screens between run causes things to show up off-screen
_ so when running, check to make sure that things are out of the area
_ PDE change indicator partially broken
_ http://code.google.com/p/processing/issues/detail?id=620
from peter n lewis
_ Use Selection For Find
_ http://code.google.com/p/processing/issues/detail?id=571
Fixes from Peter N Lewis incorporated for the next release (0196 or 1.5, whichever comes first).
X Use Selection For Find
X http://code.google.com/p/processing/issues/detail?id=571
_ Double and Triple click and drag behaviour
_ http://code.google.com/p/processing/issues/detail?id=576
_ double-clicking whitespace selects adjacent chars
@@ -33,6 +29,13 @@ _ Find Previous and FindReplace dialog cleanup
_ http://code.google.com/p/processing/issues/detail?id=580
_ bugs 67, 68, 69 (below) cover more of this
_ remove opengl2 for 1.5 and examples
2.0
_ colors for 2.0
_ nurbs or other arch stuff for 2.0?
_ Internationalization
_ http://code.google.com/p/processing/issues/detail?id=593