/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 ro.nextreports.designer.ui.sqleditor; import java.awt.BorderLayout; import java.awt.Dialog; import java.awt.Frame; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.util.regex.MatchResult; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.text.BadLocationException; import javax.swing.text.JTextComponent; import javax.swing.text.Segment; import ro.nextreports.designer.ui.EqualsLayout; import ro.nextreports.designer.ui.HistoryComboBox; import ro.nextreports.designer.util.I18NSupport; /** * @author Decebal Suiu */ public class FindReplaceDialog extends JDialog { private final JTextComponent target; private JCheckBox caseCheck; private JRadioButton allButton; private JRadioButton selectedLinesButton; private JRadioButton forwardButton; private JRadioButton backwardButton; private JCheckBox wholeWordCheck; private JButton findButton; private JButton replaceButton; private JButton replaceAllButton; private HistoryComboBox findCombo; private HistoryComboBox replaceCombo; private JCheckBox wrapCheck; private int lastFoundIndex = -1; private MatchResult lastMatchResult; private String lastRegex; private Pattern pattern; private Segment segment = new Segment(); public FindReplaceDialog(Frame parent, JTextComponent target) { super(parent, I18NSupport.getString("sqleditor.findReplaceDialog.title"), false); this.target = target; initComponents(); } public FindReplaceDialog(Dialog parent, JTextComponent target) { super(parent, I18NSupport.getString("sqleditor.findReplaceDialog.title"), false); this.target = target; initComponents(); } private void initComponents() { JPanel panel = new JPanel(new BorderLayout()); findCombo = new HistoryComboBox(); replaceCombo = new HistoryComboBox(); // create inputs JPanel inputPanel = new JPanel(new GridBagLayout()); inputPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 5, 10)); inputPanel.add(new JLabel(I18NSupport.getString("sqleditor.findReplaceDialog.find")), new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 0, 0), 0, 0)); inputPanel.add(findCombo, new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 5, 0, 1), 0, 0)); inputPanel.add(new JLabel(I18NSupport.getString("sqleditor.findReplaceDialog.replaceWith")), new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 1, 0), 0, 0)); inputPanel.add(replaceCombo, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 1, 1), 0, 0)); // create direction panel ButtonGroup directionGroup = new ButtonGroup(); forwardButton = new JRadioButton(I18NSupport.getString("sqleditor.findReplaceDialog.forward"), true); forwardButton.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); directionGroup.add(forwardButton); backwardButton = new JRadioButton(I18NSupport.getString("sqleditor.findReplaceDialog.backward")); backwardButton.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); directionGroup.add(backwardButton); JPanel directionPanel = new JPanel(new GridBagLayout()); directionPanel.setBorder(BorderFactory.createTitledBorder(I18NSupport.getString("sqleditor.findReplaceDialog.direction"))); directionPanel.add(forwardButton, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 1, 5), 0, 0)); directionPanel.add(new JLabel(""), new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 5, 1, 5), 0, 0)); directionPanel.add(backwardButton, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 1, 5), 0, 0)); // create scope panel ButtonGroup scopeGroup = new ButtonGroup(); allButton = new JRadioButton(I18NSupport.getString("sqleditor.findReplaceDialog.all"), true); allButton.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); selectedLinesButton = new JRadioButton(I18NSupport.getString("sqleditor.findReplaceDialog.selectedLines")); selectedLinesButton.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); scopeGroup.add(allButton); scopeGroup.add(selectedLinesButton); // TODO doesn't work yet selectedLinesButton.setEnabled(false); JPanel scopePanel = new JPanel(new GridBagLayout()); scopePanel.setBorder(BorderFactory.createTitledBorder(I18NSupport.getString("sqleditor.findReplaceDialog.scope"))); scopePanel.add(allButton, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 1, 5), 0, 0)); scopePanel.add(new JLabel(""), new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 5, 1, 5), 0, 0)); scopePanel.add(selectedLinesButton, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 1, 5), 0, 0)); // create options caseCheck = new JCheckBox(I18NSupport.getString("sqleditor.findReplaceDialog.caseSensitive")); caseCheck.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); wholeWordCheck = new JCheckBox(I18NSupport.getString("sqleditor.findReplaceDialog.wholeWord")); wholeWordCheck.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); wrapCheck = new JCheckBox(I18NSupport.getString("sqleditor.findReplaceDialog.wrapSearch")); wrapCheck.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); JPanel optionsPanel = new JPanel(new GridBagLayout()); optionsPanel.setBorder(BorderFactory.createTitledBorder(I18NSupport.getString("sqleditor.findReplaceDialog.options"))); optionsPanel.add(caseCheck, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 1, 5), 0, 0)); optionsPanel.add(new JLabel(""), new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 5, 1, 5), 0, 0)); // TODO doesn't work yet /* optionsPanel.add(wholeWordCheck, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 1, 5), 0, 0)); optionsPanel.add(wrapCheck, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 1, 5), 0, 0)); */ JPanel radios = new JPanel(new GridBagLayout()); radios.add(directionPanel, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); radios.add(scopePanel, new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); // create panel with options JPanel options = new JPanel(new GridBagLayout()); options.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10)); options.add(radios, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); options.add(optionsPanel, new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); // create buttons JPanel buttonsPanel = new JPanel(new EqualsLayout(5)); buttonsPanel.setBorder(BorderFactory.createEmptyBorder(10, 12, 10, 12)); findButton = new JButton(new FindAction()); buttonsPanel.add(findButton); replaceButton = new JButton(new ReplaceAction()); buttonsPanel.add(replaceButton); replaceAllButton = new JButton(new ReplaceAllAction()); buttonsPanel.add(replaceAllButton); buttonsPanel.add(new JButton(new CloseAction())); panel.add(inputPanel, BorderLayout.NORTH); panel.add(options, BorderLayout.CENTER); panel.add(buttonsPanel, BorderLayout.SOUTH); add(panel); replaceCombo.setEnabled(target.isEditable()); replaceAllButton.setEnabled(target.isEditable()); replaceButton.setEnabled(target.isEditable()); // findCombo.getEditor().selectAll(); // findCombo.requestFocus(); } private boolean doFind() { return doFind(target.getSelectionEnd()); } private boolean doFind(int startIndex) { boolean found = search(getPattern(), startIndex, backwardButton.isSelected()); if (found) { String item = (String) findCombo.getSelectedItem(); findCombo.add(item); } return found; } private boolean doReplace() { boolean replaced = doReplace(target.getSelectionStart()); if (replaced) { String item = (String) replaceCombo.getSelectedItem(); replaceCombo.add(item); } return replaced; } private boolean doReplace(int startIndex) { String findString = (String) findCombo.getSelectedItem(); String selectedText = target.getSelectedText(); if ((selectedText != null) && (selectedText.length() > 0) && selectedText.equals(findString)) { String replaceString = (String) replaceCombo.getSelectedItem(); try { target.getDocument().remove(startIndex, findString.length()); target.getDocument().insertString(startIndex, replaceString, null); } catch (BadLocationException e) { e.printStackTrace(); } return search(getPattern(), startIndex, backwardButton.isSelected()); } return false; } private int doReplaceAll() { String findString = (String) findCombo.getSelectedItem(); if (findString == null || findString.length() == 0) { return 0; } String replaceString = (String) replaceCombo.getSelectedItem(); int counter = 0; int startIndex = 0; while (doFind(startIndex)) { try { target.getDocument().remove(lastFoundIndex, findString.length()); target.getDocument().insertString(lastFoundIndex, replaceString, null); } catch (BadLocationException e) { e.printStackTrace(); } counter++; startIndex = lastFoundIndex + replaceString.length(); } return counter; } private boolean search(Pattern pattern, int startIndex, boolean backwards) { int textLength = target.getDocument().getLength(); if ((textLength == 0) || ((startIndex > -1) && (textLength < startIndex))) { updateStateAfterNotFound(); return false; } int start = startIndex; if ((startIndex >= 0) && (startIndex == lastFoundIndex)) { if (foundExtendedMatch(pattern, start)) { return true; } else { start++; } } int length = 0; if (backwards) { start = 0; if (startIndex < 0) { length = textLength - 1; // end of text } else if (startIndex > 0) { length = startIndex - 1; } } else { if (start < 0) { start = 0; // begin of text } length = textLength - start; } // System.out.println("start = " + start); // System.out.println("length = " + length); try { target.getDocument().getText(start, length, segment); } catch (BadLocationException e) { e.printStackTrace(); } Matcher matcher = pattern.matcher(segment.toString()); MatchResult matchResult = getMatchResult(matcher, !backwards); if (matchResult != null) { updateStateAfterFound(matchResult, start); return true; } else { updateStateAfterNotFound(); return false; } } /** * Search from same startIndex as the previous search. * Checks if the match is different from the last (either * extended/reduced) at the same position. Returns true * if the current match result represents a different match * than the last, false if no match or the same. */ private boolean foundExtendedMatch(Pattern pattern, int start) { if (pattern.pattern().equals(lastRegex)) { return false; } int length = target.getDocument().getLength() - start; try { target.getDocument().getText(start, length, segment); } catch (BadLocationException e) { e.printStackTrace(); } Matcher matcher = pattern.matcher(segment.toString()); MatchResult matchResult = getMatchResult(matcher, true); if (matchResult != null) { if ((matchResult.start() == 0) && (!lastMatchResult.group().equals(matchResult.group()))) { updateStateAfterFound(matchResult, start); return true; } } return false; } private MatchResult getMatchResult(Matcher matcher, boolean onlyFirst) { MatchResult matchResult = null; while (matcher.find()) { matchResult = matcher.toMatchResult(); if (onlyFirst) { break; } } return matchResult; } private int updateStateAfterFound(MatchResult matchResult, int offset) { int end = matchResult.end() + offset; int found = matchResult.start() + offset; target.select(found, end); target.getCaret().setSelectionVisible(true); // update state variables lastFoundIndex = found; lastMatchResult = matchResult; lastRegex = ((Matcher) lastMatchResult).pattern().pattern(); return found; } private void updateStateAfterNotFound() { lastMatchResult = null; lastRegex = null; lastFoundIndex = -1; } private Pattern getPattern() { String searchString = findCombo.getSelectedItem().toString(); if (searchString.length() == 0) { return null; } if ((pattern == null) || !pattern.pattern().equals(searchString) || (pattern.flags() != getPatternFlag())) { // use Pattern.LITERAL so that metacharacters or escape sequences in the // searchString will be given no special meaning. pattern = Pattern.compile(searchString, getPatternFlag() | Pattern.LITERAL); } return pattern; } private int getPatternFlag() { return caseCheck.isSelected() ? 0 : Pattern.CASE_INSENSITIVE; } private class FindAction extends AbstractAction { private static final long serialVersionUID = -8920424035964381915L; public FindAction() { super(I18NSupport.getString("sqleditor.findReplaceDialog.action.find")); } public void actionPerformed(ActionEvent event) { if (!doFind()) { if (!wrapCheck.isSelected()) { Toolkit.getDefaultToolkit().beep(); JOptionPane.showMessageDialog(FindReplaceDialog.this, I18NSupport.getString("sqleditor.findReplaceDialog.value.not.found")); } } } } private class ReplaceAction extends AbstractAction { private static final long serialVersionUID = 6520019012311957603L; public ReplaceAction() { super(I18NSupport.getString("sqleditor.findReplaceDialog.action.replace")); } public void actionPerformed(ActionEvent event) { if (!doReplace()) { if (!wrapCheck.isSelected()) { Toolkit.getDefaultToolkit().beep(); JOptionPane.showMessageDialog(FindReplaceDialog.this, I18NSupport.getString("sqleditor.findReplaceDialog.value.not.found")); } } /* if (target.getSelectedText() == null) { return; } String value = replaceCombo.getSelectedItem().toString(); int ix = target.getSelectionStart(); target.select(ix + value.length(), ix); for (int c = 0; c < replaceCombo.getItemCount(); c++) { if (replaceCombo.getItemAt(c).equals(value)) { replaceCombo.removeItem(c); break; } } replaceCombo.insertItemAt(value, 0); */ } } private class ReplaceAllAction extends AbstractAction { private static final long serialVersionUID = -2154781306570849689L; public ReplaceAllAction() { super(I18NSupport.getString("sqleditor.findReplaceDialog.action.replaceall")); } public void actionPerformed(ActionEvent event) { int counter = doReplaceAll(); String message; if (counter == 0) { Toolkit.getDefaultToolkit().beep(); message = I18NSupport.getString("sqleditor.findReplaceDialog.value.not.found"); } else { message = I18NSupport.getString("sqleditor.findReplaceDialog.replacements", counter); } JOptionPane.showMessageDialog(FindReplaceDialog.this, message); /* int caretPosition = target.getCaretPosition(); String text = target.getText(); if (findCombo.getSelectedItem() == null) { return; } String value = findCombo.getSelectedItem().toString(); if ((value.length() == 0) || (text.length() == 0)) { return; } String newValue = replaceCombo.getSelectedItem().toString(); */ } } private class CloseAction extends AbstractAction { private static final long serialVersionUID = 6227267005012383534L; public CloseAction() { super(I18NSupport.getString("sqleditor.findReplaceDialog.action.close")); } public void actionPerformed(ActionEvent event) { setVisible(false); } } }