package com.github.trytocatch.regexreplacer.ui; import java.awt.BorderLayout; import java.awt.Dialog.ModalityType; import java.awt.FlowLayout; import java.awt.GridLayout; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.Window; import java.awt.datatransfer.StringSelection; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Enumeration; import java.util.List; import java.util.regex.Pattern; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTable; import javax.swing.JTextArea; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.ToolTipManager; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableColumn; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.JTextComponent; import javax.swing.undo.UndoManager; import com.github.trytocatch.regexreplacer.model.MatchResultInfo; import com.github.trytocatch.regexreplacer.model.RegexController; import com.github.trytocatch.regexreplacer.model.expression.ResultObserver; import com.github.trytocatch.swingplus.text.LineLabel; /** * * @author [email protected] * @date 2012-12-21 */ public class SearchPanel extends JPanel implements ResultObserver { private static final long serialVersionUID = -1619683153966533649L; private static final float CENTER = 0.5f; private OutPutDialog outPutDlg; private RegexController regexController; private boolean isResultDisplayed; private JTextComponent editArea; private DocumentListener editorDocumentListener; private MyTableModel resultTableModel; private JTable resultTable; private JTextArea regexArea; private JTextArea replaceArea; private JCheckBox unixLinesCkb; private JCheckBox caseInsensitiveCkb; private JCheckBox commentsCkb; private JCheckBox multilineCkb; private JCheckBox literalCkb; private JCheckBox dotallCkb; private JCheckBox unicodeCaseCkb; private JCheckBox canonEqCkb; private JCheckBox liveUpdateCkb; private JCheckBox outputResultToNewWindow; private JCheckBox divertFocus; private JCheckBox expressionAvailable; private JButton updateNowBtn; private JButton replaceSelected; private JButton replaceAll; private JLabel statsLabel; private JLabel matchResultLabel; public SearchPanel(JTextComponent editArea) { if (editArea == null) throw new IllegalArgumentException("editArea can not be null"); this.editArea = editArea; regexController = new RegexController(); regexController.setResultObserver(this); isResultDisplayed = true; outPutDlg = new OutPutDialog(); initComponent(); } public RegexController getRegexController() { return regexController; } public void setRegexController(RegexController regexController) { if (regexController == null) throw new IllegalArgumentException("RegexController can't be null"); this.regexController = regexController; } private void createComponent() { resultTableModel = new MyTableModel(); resultTable = new JTable(resultTableModel); Enumeration<TableColumn> columns = resultTable.getColumnModel() .getColumns(); for (int n = 0; columns.hasMoreElements(); n++) { columns.nextElement().setPreferredWidth( resultTableModel.getColumnWidth(n)); } resultTable.setPreferredScrollableViewportSize(resultTable .getPreferredSize()); resultTable.setAutoCreateRowSorter(false); // resultTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); regexArea = new JTextArea(3, 20); replaceArea = new JTextArea(3, 20); unixLinesCkb = new JCheckBox("UNIX_LINES"); unixLinesCkb.setToolTipText(Helper.UNIX_LINES_TIP); caseInsensitiveCkb = new JCheckBox("CASE_INSENSITIVE"); caseInsensitiveCkb.setToolTipText(Helper.CASE_INSENSITIVE_TIP); commentsCkb = new JCheckBox("COMMENTS"); commentsCkb.setToolTipText(Helper.COMMENTS_TIP); multilineCkb = new JCheckBox("MULTILINE"); multilineCkb.setToolTipText(Helper.MULTILINE_TIP); literalCkb = new JCheckBox(StrUtils.getStr("SearchPanel.LITERAL")); literalCkb.setToolTipText(Helper.LITERAL_TIP); dotallCkb = new JCheckBox("DOTALL"); dotallCkb.setToolTipText(Helper.DOTALL_TIP); unicodeCaseCkb = new JCheckBox("UNICODE_CASE"); unicodeCaseCkb.setToolTipText(Helper.UNICODE_CASE_TIP); canonEqCkb = new JCheckBox("CANON_EQ"); canonEqCkb.setToolTipText(Helper.CANON_EQ_TIP); expressionAvailable = new JCheckBox( StrUtils.getStr("SearchPanel.replaceFunction"), true); expressionAvailable.setToolTipText(StrUtils .getStr("SearchPanel.replaceFunction_tip")); liveUpdateCkb = new JCheckBox( StrUtils.getStr("SearchPanel.liveUpdate"), true); outputResultToNewWindow = new JCheckBox( StrUtils.getStr("SearchPanel.getReplacementOnly")); outputResultToNewWindow.setToolTipText(StrUtils .getStr("SearchPanel.getReplacementOnly_tip")); divertFocus = new JCheckBox(StrUtils.getStr("SearchPanel.returnFocus"), true); divertFocus.setToolTipText(StrUtils .getStr("SearchPanel.returnFocus_tip")); updateNowBtn = new JButton(StrUtils.getStr("SearchPanel.udpate")); updateNowBtn.setEnabled(false); replaceSelected = new JButton( StrUtils.getStr("SearchPanel.replaceSelected")); replaceAll = new JButton(StrUtils.getStr("SearchPanel.replaceAll")); statsLabel = new JLabel(StrUtils.getStr("SearchPanel.authorLabel")); matchResultLabel = new JLabel(); } private int getRegexFlag() { int flag = 0; if (unixLinesCkb.isSelected()) flag |= Pattern.UNIX_LINES; if (caseInsensitiveCkb.isSelected()) flag |= Pattern.CASE_INSENSITIVE; if (commentsCkb.isSelected()) flag |= Pattern.COMMENTS; if (multilineCkb.isSelected()) flag |= Pattern.MULTILINE; if (literalCkb.isSelected()) flag |= Pattern.LITERAL; if (dotallCkb.isSelected()) flag |= Pattern.DOTALL; if (unicodeCaseCkb.isSelected()) flag |= Pattern.UNICODE_CASE; if (canonEqCkb.isSelected()) flag |= Pattern.CANON_EQ; return flag; } @SuppressWarnings("serial") private void installListener() { ItemListener flagListener = new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { regexController.setPatternFlag(getRegexFlag()); } }; unixLinesCkb.addItemListener(flagListener); caseInsensitiveCkb.addItemListener(flagListener); commentsCkb.addItemListener(flagListener); multilineCkb.addItemListener(flagListener); literalCkb.addItemListener(flagListener); dotallCkb.addItemListener(flagListener); unicodeCaseCkb.addItemListener(flagListener); canonEqCkb.addItemListener(flagListener); editorDocumentListener = new DocumentListener() { @Override public void removeUpdate(DocumentEvent e) { regexController.setText(editArea.getText()); } @Override public void insertUpdate(DocumentEvent e) { regexController.setText(editArea.getText()); } @Override public void changedUpdate(DocumentEvent e) { // needed? regexController.setText(editArea.getText()); } }; editArea.getDocument().addDocumentListener(editorDocumentListener); editArea.addPropertyChangeListener("document", new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { ((Document) evt.getOldValue()) .removeDocumentListener(editorDocumentListener); ((Document) evt.getNewValue()) .addDocumentListener(editorDocumentListener); } }); regexArea.getDocument().addDocumentListener(new DocumentListener() { @Override public void removeUpdate(DocumentEvent e) { regexController.setRegexStr(regexArea.getText()); } @Override public void insertUpdate(DocumentEvent e) { regexController.setRegexStr(regexArea.getText()); } @Override public void changedUpdate(DocumentEvent e) { // needed? regexController.setRegexStr(regexArea.getText()); } }); final UndoManager regexAreaundoManager = new UndoManager(); regexArea.getDocument().addUndoableEditListener(regexAreaundoManager); regexArea.getInputMap().put(KeyStroke.getKeyStroke("control Z"), "undo"); regexArea.getActionMap().put("undo", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (regexAreaundoManager.canUndo()) regexAreaundoManager.undo(); } }); regexArea.getInputMap().put(KeyStroke.getKeyStroke("control Y"), "redo"); regexArea.getActionMap().put("redo", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (regexAreaundoManager.canRedo()) regexAreaundoManager.redo(); } }); replaceArea.getDocument().addDocumentListener(new DocumentListener() { @Override public void removeUpdate(DocumentEvent e) { regexController.setReplaceExpression(replaceArea.getText()); } @Override public void insertUpdate(DocumentEvent e) { regexController.setReplaceExpression(replaceArea.getText()); } @Override public void changedUpdate(DocumentEvent e) { // needed? regexController.setReplaceExpression(replaceArea.getText()); } }); final UndoManager replaceAreaUndoManager = new UndoManager(); replaceArea.getDocument().addUndoableEditListener(replaceAreaUndoManager); replaceArea.getInputMap().put(KeyStroke.getKeyStroke("control Z"), "undo"); replaceArea.getActionMap().put("undo", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (replaceAreaUndoManager.canUndo()) replaceAreaUndoManager.undo(); } }); replaceArea.getInputMap().put(KeyStroke.getKeyStroke("control Y"), "redo"); replaceArea.getActionMap().put("redo", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (replaceAreaUndoManager.canRedo()) replaceAreaUndoManager.redo(); } }); resultTable.addMouseListener(new MouseAdapter() { @Override public void mouseReleased(MouseEvent e) { selectMatchedText(resultTableModel.getRowObject(resultTable .getSelectionModel().getLeadSelectionIndex())); } }); expressionAvailable.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { regexController.setExpressionAvailable(expressionAvailable .isSelected()); } }); liveUpdateCkb.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { updateNowBtn.setEnabled(!liveUpdateCkb.isSelected()); regexController.setLiveUpdate(liveUpdateCkb.isSelected()); } }); ActionListener buttonsActions = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Object source = e.getSource(); if (updateNowBtn == source) regexController.update(); else if (replaceSelected == source) doReplace(false); else if (replaceAll == source) doReplace(true); } }; updateNowBtn.addActionListener(buttonsActions); replaceSelected.addActionListener(buttonsActions); replaceAll.addActionListener(buttonsActions); } private void initComponent() { JLabel labelTemp; createComponent(); installListener(); ToolTipManager m = ToolTipManager.sharedInstance(); m.setDismissDelay(30000); m.setReshowDelay(800); setLayout(new BorderLayout(0, 3)); JPanel leftPanel = new JPanel(); leftPanel.setLayout(new BoxLayout(leftPanel, BoxLayout.Y_AXIS)); labelTemp = new JLabel(StrUtils.getStr("SearchPanel.regexFlags")); labelTemp.setAlignmentX(CENTER); leftPanel.add(labelTemp); JPanel flagPanel = new JPanel(); flagPanel.setLayout(new GridLayout(4, 2, 0, 0)); flagPanel.add(unixLinesCkb); flagPanel.add(caseInsensitiveCkb); flagPanel.add(commentsCkb); flagPanel.add(multilineCkb); flagPanel.add(literalCkb); flagPanel.add(dotallCkb); flagPanel.add(unicodeCaseCkb); flagPanel.add(canonEqCkb); flagPanel.setMaximumSize(flagPanel.getPreferredSize()); leftPanel.add(flagPanel); labelTemp = new JLabel(StrUtils.getStr("SearchPanel.regularExpression")); labelTemp.setAlignmentX(CENTER); leftPanel.add(labelTemp); leftPanel.add(new JScrollPane(regexArea)); labelTemp = new JLabel(StrUtils.getStr("SearchPanel.replaceExpression")); labelTemp.setAlignmentX(CENTER); leftPanel.add(labelTemp); leftPanel.add(new JScrollPane(replaceArea)); JPanel rightPanel = new JPanel(); rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.Y_AXIS)); labelTemp = new JLabel(StrUtils.getStr("SearchPanel.matchResult")); labelTemp.setAlignmentX(CENTER); rightPanel.add(labelTemp); rightPanel.add(new JScrollPane(resultTable, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)); JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel); splitPane.setDividerSize(3); JPanel leftButtonsPanel = new JPanel(); JPanel rightButtonsPanel = new JPanel(); leftButtonsPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 15, 0)); rightButtonsPanel.setLayout(new FlowLayout(FlowLayout.RIGHT, 15, 0)); leftButtonsPanel.add(expressionAvailable); leftButtonsPanel.add(liveUpdateCkb); leftButtonsPanel.add(updateNowBtn); rightButtonsPanel.add(divertFocus); rightButtonsPanel.add(outputResultToNewWindow); rightButtonsPanel.add(replaceAll); rightButtonsPanel.add(replaceSelected); JPanel buttonsPanel = new JPanel(); buttonsPanel.setLayout(new BorderLayout()); buttonsPanel.add(leftButtonsPanel, BorderLayout.WEST); buttonsPanel.add(rightButtonsPanel, BorderLayout.EAST); JPanel centerPanel = new JPanel(); centerPanel.setLayout(new BorderLayout()); centerPanel.add(splitPane); centerPanel.add(buttonsPanel, BorderLayout.SOUTH); this.add(centerPanel, BorderLayout.CENTER); JPanel statsPanel = new JPanel(); statsPanel.setLayout(new BorderLayout()); statsPanel.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, statsLabel.getBackground().darker())); statsPanel.add(statsLabel, BorderLayout.WEST); statsPanel.add(matchResultLabel, BorderLayout.EAST); // ensure statsLabel has same height while setText(null) statsPanel.setPreferredSize(statsLabel.getPreferredSize()); this.add(statsPanel, BorderLayout.SOUTH); } private void selectMatchedText(MatchResultInfo resultInfo) { if (regexController.isResultUptodate() == false) statsLabel.setText(StrUtils.getStr("SearchPanel.resultOutOfDate")); else if (resultInfo != null && divertFocus.isSelected()) { try { Rectangle r = editArea.getUI().modelToView(editArea, resultInfo.getEndPos()); editArea.scrollRectToVisible(r); editArea.setCaretPosition(resultInfo.getEndPos()); editArea.moveCaretPosition(resultInfo.getStartPos()); editArea.requestFocusInWindow(); } catch (BadLocationException e) { } } } private void doReplace(boolean isReplaceAll) { if (regexController.isResultUptodate() == false) { statsLabel.setText(StrUtils.getStr("SearchPanel.resultOutOfDate")); return; } List<MatchResultInfo> result; if (isReplaceAll) if (regexController.update() == RegexController.UPDATE_COMMITTED || isResultDisplayed == false) { statsLabel.setText(StrUtils .getStr("SearchPanel.replacingCanceled")); return; } else { result = resultTableModel.getAllData(); if (result == null || result.size() == 0) { statsLabel.setText(StrUtils .getStr("SearchPanel.resultIsEmpty")); return; } } else { int[] indexes = resultTable.getSelectedRows(); if (indexes.length == 0) { statsLabel.setText(StrUtils .getStr("SearchPanel.selectionIsEmpty")); return; } else try { result = regexController.getRealReplaceResult(indexes); if (isResultDisplayed == false) { statsLabel.setText(StrUtils .getStr("SearchPanel.replacingCanceled")); return; } } catch (Exception e) { statsLabel.setText(StrUtils .getStr("SearchPanel.replacingCanceled")); return; } } if (result != null) { StringBuilder targetText = new StringBuilder(); String originalText = editArea.getText(); if (outputResultToNewWindow.isSelected()) { for (MatchResultInfo match : result) targetText.append(match.getReplaceStr()); outPutDlg.showOutPutDlg( SwingUtilities.windowForComponent(this), targetText.toString()); } else { int oldStartPos = 0; for (MatchResultInfo match : result) { targetText.append(originalText, oldStartPos, match.getStartPos()); targetText.append(match.getReplaceStr()); oldStartPos = match.getEndPos(); } targetText.append(originalText, oldStartPos, originalText.length()); editArea.setText(targetText.toString()); } } } @Override public void onStart() { matchResultLabel.setText(StrUtils.getStr("SearchPanel.calculating")); } @Override public void onResultChanged(final List<MatchResultInfo> result, final String errorInfo) { isResultDisplayed = false; SwingUtilities.invokeLater(new Runnable() { @Override public void run() { statsLabel.setText(errorInfo); resultTableModel.setData(result); matchResultLabel.setText(StrUtils.getStr( "SearchPanel.resultLabel", result == null ? 0 : result.size())); isResultDisplayed = true; } }); } static private class OutPutDialog { private JTextArea outPutArea = new JTextArea(); private JDialog dlg; synchronized JDialog getDlgInstance(Window owner) { if (dlg == null) { dlg = new JDialog(owner, StrUtils.getStr("SearchPanel.replacement"), ModalityType.DOCUMENT_MODAL); dlg.setSize(500, 500); JScrollPane jsp = new JScrollPane(outPutArea); jsp.setRowHeaderView(new LineLabel(outPutArea)); dlg.getContentPane().add(jsp); dlg.getContentPane().add( new JButton(new AbstractAction( StrUtils.getStr("SearchPanel.copyAndClose")) { private static final long serialVersionUID = -4859439907152041642L; @Override public void actionPerformed(ActionEvent e) { Toolkit.getDefaultToolkit() .getSystemClipboard() .setContents( new StringSelection(outPutArea .getText()), null); dlg.dispose(); } }), BorderLayout.SOUTH); dlg.setLocationRelativeTo(owner); } return dlg; } void showOutPutDlg(Window owner, String result) { outPutArea.setText(result); getDlgInstance(owner).setVisible(true); } } static class MyTableModel extends AbstractTableModel { private static final long serialVersionUID = 8375142542174693067L; private static String[] columnNames = { StrUtils.getStr("SearchPanel.sequence"), StrUtils.getStr("SearchPanel.matchedContent"), StrUtils.getStr("SearchPanel.replacement"), StrUtils.getStr("SearchPanel.posStart"), StrUtils.getStr("SearchPanel.posEnd") }; private static int[] columnWidth = { 50, 150, 150, 45, 45 }; private List<MatchResultInfo> data; void setData(List<MatchResultInfo> data) { this.data = data; fireTableDataChanged(); } /** may be null */ List<MatchResultInfo> getAllData() { return data; } MatchResultInfo getRowObject(int index) { if (data == null || index < 0 || index >= data.size()) return null; else return data.get(index); } @Override public int getRowCount() { return data == null ? 0 : data.size(); } @Override public int getColumnCount() { return columnNames.length; } @Override public Object getValueAt(int rowIndex, int columnIndex) { if (data == null) return null; switch (columnIndex) { case 0: return rowIndex + 1; case 1: return data.get(rowIndex).getMatchedStr(); case 2: return data.get(rowIndex).getReplaceStr(); case 3: return data.get(rowIndex).getStartPos(); case 4: return data.get(rowIndex).getEndPos(); } return null; } @Override public String getColumnName(int column) { return columnNames[column]; } public int getColumnWidth(int column) { return columnWidth[column]; } } static private class Helper { public static final String UNIX_LINES_TIP = StrUtils .getStr("SearchPanel.UNIX_LINES_TIP"); public static final String CASE_INSENSITIVE_TIP = StrUtils .getStr("SearchPanel.CASE_INSENSITIVE_TIP"); public static final String COMMENTS_TIP = StrUtils .getStr("SearchPanel.COMMENTS_TIP"); public static final String MULTILINE_TIP = StrUtils .getStr("SearchPanel.MULTILINE_TIP"); public static final String LITERAL_TIP = StrUtils .getStr("SearchPanel.LITERAL_TIP"); public static final String DOTALL_TIP = StrUtils .getStr("SearchPanel.DOTALL_TIP"); public static final String UNICODE_CASE_TIP = StrUtils .getStr("SearchPanel.UNICODE_CASE_TIP"); public static final String CANON_EQ_TIP = StrUtils .getStr("SearchPanel.CANON_EQ_TIP"); } }