package org.reldb.dbrowser.ui.content.cmd;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ExtendedModifyEvent;
import org.eclipse.swt.custom.ExtendedModifyListener;
import org.eclipse.swt.custom.LineBackgroundEvent;
import org.eclipse.swt.custom.LineBackgroundListener;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.widgets.Dialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.wb.swt.SWTResourceManager;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.RowLayout;
public class FindReplace extends Dialog {
private Shell shell;
private Label lblFind;
private Text textFind;
private Label lblReplace;
private Text textReplace;
private Composite compositeDirectionScope;
private Group grpDirection;
private Button btnRadioForward;
private Button btnRadioBackward;
private Composite compositeButtons;
private Group grpScope;
private Button btnRadioAll;
private Button btnRadioSelected;
private Composite compositeOptions;
private Group grpOptions;
private Button btnCheckCaseSensitive;
private Button btnCheckWholeWord;
private Button btnCheckRegexp;
private Button btnCheckWrapsearch;
private Button btnCheckIncremental;
private Button btnFind;
private Button btnReplaceFind;
private Button btnReplace;
private Button btnReplaceAll;
private Composite compositeStatusAndClose;
private Label lblStatus;
private StyledText text;
private Vector<Match> matches = null;
private int lastFindIndex = -1;
private Point lastReplacementRange = null;
private int preservedCaretOffset;
private Point originalTextSelection;
private static final Color originalSelectionHighlightColor = SWTResourceManager.getColor(255, 200, 200);
/**
* Create the dialog.
* @param parent
* @param style
*/
public FindReplace(Shell parent, StyledText text) {
super(parent, SWT.DIALOG_TRIM | SWT.RESIZE);
this.text = text;
setText("Find/Replace");
text.addExtendedModifyListener(textModifyListener);
text.addSelectionListener(textSelectionListener);
text.addFocusListener(textFocusListener);
text.addLineBackgroundListener(textLineBackgroundListener);
originalTextSelection = text.getSelectionRange();
}
private void refreshText() {
int topIndex = text.getTopIndex(); // this guarantees a redraw, where text.redraw() does not
text.setTopIndex(text.getLineCount());
text.setTopIndex(topIndex);
}
/**
* Open the dialog.
* @return the result
*/
public Object open() {
createContents();
shell.open();
shell.layout();
textFind.setText(text.getSelectionText());
textFind.setSelection(0, text.getCharCount());
refreshText();
Display display = getParent().getDisplay();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
return null;
}
private Timer textModifyTimer = new Timer();
private ExtendedModifyListener textModifyListener = new ExtendedModifyListener() {
@Override
public void modifyText(ExtendedModifyEvent event) {
clearAll();
lastFindIndex = -1;
originalTextSelection = new Point(0, 0);
textModifyTimer.cancel();
textModifyTimer = new Timer();
textModifyTimer.schedule(new TimerTask() {
@Override
public void run() {
textModifyTimer.cancel();
Display.getDefault().syncExec(new Runnable() {
public void run() {
clearAll();
doFind();
}
});
}
}, 250);
}
};
private SelectionListener textSelectionListener = new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
boolean selected = e.y - e.x > 0;
btnReplace.setEnabled(selected);
btnReplaceFind.setEnabled(selected);
}
};
private FocusListener textFocusListener = new FocusListener() {
@Override
public void focusLost(FocusEvent e) {
preservedCaretOffset = getCaretOffset();
}
@Override
public void focusGained(FocusEvent e) {
lastFindIndex = -1;
}
};
private Point getOriginalSelectionLines() {
int startLine = text.getLineAtOffset(originalTextSelection.x);
int endLine = text.getLineAtOffset(originalTextSelection.x + originalTextSelection.y);
return new Point(startLine, endLine);
}
private Point getOriginalSelectionLineOffsets() {
Point originalSelectionLines = getOriginalSelectionLines();
int lineCount = text.getLineCount();
int start = text.getOffsetAtLine(originalSelectionLines.x);
int end;
if (originalSelectionLines.y >= lineCount - 1)
end = text.getCharCount();
else
end = text.getOffsetAtLine(originalSelectionLines.y + 1) - 1;
return new Point(start, end);
}
private boolean isOverlappingOriginalSelectionLineOffsetRange(int start, int length) {
int end = start + length;
Point originalSelectionLineRange = getOriginalSelectionLineOffsets();
return (start <= originalSelectionLineRange.y) && (end >= originalSelectionLineRange.x);
}
private LineBackgroundListener textLineBackgroundListener = new LineBackgroundListener() {
@Override
public void lineGetBackground(LineBackgroundEvent event) {
if (!btnRadioSelected.getSelection())
return;
if (isOverlappingOriginalSelectionLineOffsetRange(event.lineOffset, event.lineText.length()))
event.lineBackground = originalSelectionHighlightColor;
}
};
private int getCaretOffset() {
if (text.isFocusControl())
return text.getCaretOffset();
else
return preservedCaretOffset;
}
private void setCaretOffset(int position) {
text.setCaretOffset(position);
preservedCaretOffset = position;
}
private void setStatus(String error) {
lblStatus.setText(error);
compositeStatusAndClose.layout();
}
private Pattern compilePattern() {
String needle = textFind.getText().trim();
if (needle.length() == 0)
return null;
String regexp;
if (btnCheckWholeWord.getSelection())
regexp = "\\b(" + Pattern.quote(needle) + ")\\b";
else if (!btnCheckRegexp.getSelection())
regexp = Pattern.quote(needle);
else
regexp = needle;
try {
return Pattern.compile(regexp, (!btnCheckCaseSensitive.getSelection()) ? Pattern.CASE_INSENSITIVE : 0);
} catch (PatternSyntaxException pse) {
String error = "Regex error: " + pse.getMessage();
setStatus(error);
return null;
}
}
private void clearAll() {
setStatus("");
matches = null;
text.setSelectionRange(0, 0);
btnReplace.setEnabled(false);
btnReplaceFind.setEnabled(false);
}
private class Match {
public int start;
public int end;
public Match(int start, int end) {this.start = start; this.end = end;}
}
private void buildSearchResults() {
matches = new Vector<Match>();
Pattern pattern = compilePattern();
if (pattern == null)
return;
Matcher matcher = pattern.matcher(text.getText());
if (btnRadioSelected.getSelection())
while (matcher.find()) {
if (!isOverlappingOriginalSelectionLineOffsetRange(matcher.start(), matcher.end() - matcher.start()))
continue;
matches.add(new Match(matcher.start(), matcher.end()));
}
else
while (matcher.find())
matches.add(new Match(matcher.start(), matcher.end()));
}
private void doFindInternal() {
if (getCaretOffset() >= text.getCharCount())
setCaretOffset(0);
if (matches == null)
buildSearchResults();
if (matches.size() == 0) {
setStatus("Not found.");
return;
}
if (lastFindIndex >= matches.size()) {
if (btnCheckWrapsearch.getSelection()) {
lastFindIndex = 0;
setStatus("Wrapped to the beginning.");
} else {
lastFindIndex = matches.size() - 1;
setStatus("Reached the end.");
return;
}
} else if (lastFindIndex < 0) {
if (btnRadioForward.getSelection()) {
lastFindIndex = 0;
for (Match match: matches) {
if (match.start >= getCaretOffset())
break;
lastFindIndex++;
}
if (lastFindIndex >= matches.size())
lastFindIndex = matches.size() - 1;
} else {
lastFindIndex = matches.size() - 1;
while (lastFindIndex > 0) {
Match match = matches.get(lastFindIndex);
if (match.start <= getCaretOffset())
break;
lastFindIndex--;
}
}
}
Match match = matches.get(lastFindIndex);
text.setSelection(match.start, match.end);
setCaretOffset(match.end);
btnReplace.setEnabled(true);
btnReplaceFind.setEnabled(true);
}
private void doFind() {
setStatus("");
if (lastReplacementRange != null) {
text.setSelectionRange(lastReplacementRange.x, lastReplacementRange.y);
setCaretOffset(lastReplacementRange.x + lastReplacementRange.y);
lastReplacementRange = null;
} else
doFindInternal();
}
private void doFindNext() {
setStatus("");
if (lastFindIndex >= 0) {
if (btnRadioForward.getSelection())
lastFindIndex++;
else {
lastFindIndex--;
if (lastFindIndex < 0) {
if (btnCheckWrapsearch.getSelection()) {
lastFindIndex = matches.size() - 1;
setStatus("Wrapped to the end.");
} else {
lastFindIndex = 0;
setStatus("Reached the beginning.");
return;
}
}
}
}
doFindInternal();
}
protected void doReplaceAll() {
setStatus("");
Pattern pattern = compilePattern();
if (pattern == null)
return;
String haystack = text.getText();
long hitCount = 0;
Matcher matcher = pattern.matcher(haystack);
StringBuffer changeBuffer = new StringBuffer();
while (matcher.find()) {
if (btnRadioSelected.getSelection())
if (!isOverlappingOriginalSelectionLineOffsetRange(matcher.start(), matcher.end() - matcher.start()))
continue;
matcher.appendReplacement(changeBuffer, textReplace.getText());
hitCount++;
}
matcher.appendTail(changeBuffer);
text.removeExtendedModifyListener(textModifyListener);
int topLine = text.getTopIndex();
text.setText(changeBuffer.toString());
text.redraw();
text.setTopIndex(topLine);
text.addExtendedModifyListener(textModifyListener);
if (hitCount == 0)
setStatus("Not found.");
else if (hitCount == 1)
setStatus(hitCount + " match replaced.");
else
setStatus(hitCount + " matches replaced.");
}
protected void doReplace() {
setStatus("");
Pattern pattern = compilePattern();
if (pattern == null)
return;
String haystack = text.getSelectionText();
Matcher matcher = pattern.matcher(haystack);
StringBuffer changeBuffer = new StringBuffer();
if (matcher.find())
matcher.appendReplacement(changeBuffer, textReplace.getText());
else {
setStatus("Not found.");
return;
}
int start = text.getSelectionRange().x;
int length = text.getSelectionRange().y;
text.replaceTextRange(start, length, changeBuffer.toString());
int replacementLength = changeBuffer.toString().length();
lastReplacementRange = new Point(start, replacementLength);
setCaretOffset(start + replacementLength);
lastFindIndex = -1;
}
/**
* Create contents of the dialog.
*/
private void createContents() {
shell = new Shell(getParent(), getStyle());
shell.setText("Find/Replace");
shell.setLayout(new FormLayout());
Composite searchTextPanel = new Composite(shell, SWT.None);
searchTextPanel.setLayout(new GridLayout(3, false));
FormData fd_searchTextPanel = new FormData();
fd_searchTextPanel.right = new FormAttachment(100);
fd_searchTextPanel.top = new FormAttachment(0);
fd_searchTextPanel.left = new FormAttachment(0);
searchTextPanel.setLayoutData(fd_searchTextPanel);
new Label(searchTextPanel, SWT.NONE);
lblFind = new Label(searchTextPanel, SWT.NONE);
lblFind.setText("Find:");
lblFind.setAlignment(SWT.RIGHT);
lblFind.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
textFind = new Text(searchTextPanel, SWT.BORDER);
textFind.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
boolean findEmpty = textFind.getText().isEmpty();
btnFind.setEnabled(!findEmpty);
btnReplaceAll.setEnabled(!findEmpty);
clearAll();
if (!btnCheckIncremental.getSelection())
return;
doFind();
}
});
textFind.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
if (e.keyCode == '\r')
doFindNext();
}
});
textFind.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
new Label(searchTextPanel, SWT.NONE);
lblReplace = new Label(searchTextPanel, SWT.NONE);
lblReplace.setText("Replace with:");
lblReplace.setAlignment(SWT.RIGHT);
lblReplace.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
textReplace = new Text(searchTextPanel, SWT.BORDER);
textReplace.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
Composite contentPanel = new Composite(shell, SWT.NONE);
contentPanel.setLayout(new GridLayout(3, false));
FormData fd_contentPanel = new FormData();
fd_contentPanel.top = new FormAttachment(searchTextPanel);
fd_contentPanel.left = new FormAttachment(0);
fd_contentPanel.right = new FormAttachment(100);
contentPanel.setLayoutData(fd_contentPanel);
compositeDirectionScope = new Composite(contentPanel, SWT.NONE);
compositeDirectionScope.setLayout(new FillLayout(SWT.HORIZONTAL));
compositeDirectionScope.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 3, 1));
grpDirection = new Group(compositeDirectionScope, SWT.NONE);
grpDirection.setText("Direction");
grpDirection.setLayout(new RowLayout(SWT.VERTICAL));
btnRadioForward = new Button(grpDirection, SWT.RADIO);
btnRadioForward.setSelection(true);
btnRadioForward.setText("Forward");
btnRadioBackward = new Button(grpDirection, SWT.RADIO);
btnRadioBackward.setText("Backward");
grpScope = new Group(compositeDirectionScope, SWT.NONE);
grpScope.setText("Scope");
grpScope.setLayout(new RowLayout(SWT.VERTICAL));
btnRadioAll = new Button(grpScope, SWT.RADIO);
btnRadioAll.setText("All");
btnRadioAll.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
lastFindIndex = -1;
refreshText();
clearAll();
doFind();
}
});
btnRadioAll.setSelection(true);
btnRadioSelected = new Button(grpScope, SWT.RADIO);
btnRadioSelected.setText("Selected lines");
btnRadioSelected.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
lastFindIndex = -1;
refreshText();
clearAll();
doFind();
}
});
compositeOptions = new Composite(contentPanel, SWT.NONE);
compositeOptions.setLayout(new FillLayout(SWT.HORIZONTAL));
compositeOptions.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 3, 1));
grpOptions = new Group(compositeOptions, SWT.NONE);
grpOptions.setText("Options");
grpOptions.setLayout(new GridLayout(2, false));
btnCheckCaseSensitive = new Button(grpOptions, SWT.CHECK);
btnCheckCaseSensitive.setText("Case sensitive");
btnCheckCaseSensitive.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
clearAll();
doFind();
}
});
btnCheckWholeWord = new Button(grpOptions, SWT.CHECK);
btnCheckWholeWord.setText("Whole word");
btnCheckWholeWord.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
clearAll();
doFind();
}
});
btnCheckRegexp = new Button(grpOptions, SWT.CHECK);
btnCheckRegexp.setText("Regular expressions");
btnCheckRegexp.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
clearAll();
doFind();
btnCheckIncremental.setEnabled(!btnCheckRegexp.getSelection());
btnCheckWholeWord.setEnabled(!btnCheckRegexp.getSelection());
}
});
btnCheckWrapsearch = new Button(grpOptions, SWT.CHECK);
btnCheckWrapsearch.setText("Wrap search");
btnCheckIncremental = new Button(grpOptions, SWT.CHECK);
btnCheckIncremental.setText("Incremental");
btnCheckIncremental.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
clearAll();
doFind();
}
});
new Label(grpOptions, SWT.NONE);
compositeButtons = new Composite(contentPanel, SWT.NONE);
compositeButtons.setLayout(new GridLayout(3, true));
compositeButtons.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true, 3, 1));
btnFind = new Button(compositeButtons, SWT.NONE);
btnFind.setText("Find");
btnFind.setEnabled(false);
btnFind.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
doFindNext();
}
});
btnFind.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
new Label(compositeButtons, SWT.NONE);
Composite composite = new Composite(compositeButtons, SWT.NONE);
composite.setLayout(new FillLayout(SWT.HORIZONTAL));
btnReplaceFind = new Button(compositeButtons, SWT.NONE);
btnReplaceFind.setText("Replace/Find");
btnReplaceFind.setEnabled(false);
btnReplaceFind.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
doReplace();
doFindNext();
}
});
btnReplaceFind.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
btnReplace = new Button(compositeButtons, SWT.NONE);
btnReplace.setText("Replace");
btnReplace.setEnabled(false);
btnReplace.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
doReplace();
}
});
btnReplace.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
btnReplaceAll = new Button(compositeButtons, SWT.NONE);
btnReplaceAll.setText("Replace All");
btnReplaceAll.setEnabled(false);
btnReplaceAll.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
doReplaceAll();
}
});
btnReplaceAll.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
compositeStatusAndClose = new Composite(shell, SWT.NONE);
fd_contentPanel.bottom = new FormAttachment(compositeStatusAndClose);
FormData fd_compositeStatusAndClose = new FormData();
fd_compositeStatusAndClose.bottom = new FormAttachment(100);
fd_compositeStatusAndClose.right = new FormAttachment(100);
fd_compositeStatusAndClose.left = new FormAttachment(0);
compositeStatusAndClose.setLayoutData(fd_compositeStatusAndClose);
compositeStatusAndClose.setLayout(new GridLayout(2, false));
lblStatus = new Label(compositeStatusAndClose, SWT.WRAP);
lblStatus.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, true, 1, 1));
Button btnClose = new Button(compositeStatusAndClose, SWT.RIGHT);
btnClose.setText("Close");
btnClose.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
btnClose.setAlignment(SWT.CENTER);
btnClose.setFocus();
btnClose.setSize(btnClose.computeSize(SWT.DEFAULT, SWT.DEFAULT));
btnClose.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
shell.dispose();
}
});
shell.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
text.removeExtendedModifyListener(textModifyListener);
text.removeSelectionListener(textSelectionListener);
text.removeFocusListener(textFocusListener);
text.removeLineBackgroundListener(textLineBackgroundListener);
refreshText();
text.setFocus();
}
});
shell.pack();
}
}