/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenFlexo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.openflexo.search.view; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Vector; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.ButtonGroup; import javax.swing.DefaultComboBoxModel; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JTextField; import javax.swing.SwingUtilities; import javax.swing.event.CaretEvent; import javax.swing.event.CaretListener; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.text.JTextComponent; import org.openflexo.localization.FlexoLocalization; import org.openflexo.search.EndOfDocumentHasBeenReachedException; import org.openflexo.search.ResultNotFoundException; import org.openflexo.search.TextQuery; import org.openflexo.search.TextQueryEngine; import org.openflexo.search.TextQueryResult; import org.openflexo.search.TextQueryResult.Direction; import org.openflexo.search.TextQueryResult.Result; public class TextSearchPanel extends JPanel implements CaretListener { public class FindReplaceFieldPanel extends JPanel implements ActionListener { protected JComboBox findField; protected JComboBox replaceField; public FindReplaceFieldPanel() { super(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.WEST; gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 0; gbc.weighty = 0; gbc.insets = new Insets(5, 5, 5, 5); JLabel find = new JLabel(); find.setText(FlexoLocalization.localizedForKey("find")); add(find, gbc); findField = new JComboBox(new DefaultComboBoxModel(new Vector<String>())); findField.setEditable(true); if (query.getSearchedText() != null && query.getSearchedText().length() != 0) { ((DefaultComboBoxModel) findField.getModel()).insertElementAt(query.getSearchedText(), 0); findField.setSelectedIndex(0); } if (findField.getEditor().getEditorComponent() instanceof JTextComponent) { ((JTextComponent) findField.getEditor().getEditorComponent()).getDocument().addDocumentListener(new DocumentListener() { @Override public void changedUpdate(DocumentEvent e) { resultNeedsUpdate = true; updateButtons(); } @Override public void insertUpdate(DocumentEvent e) { resultNeedsUpdate = true; updateButtons(); } @Override public void removeUpdate(DocumentEvent e) { resultNeedsUpdate = true; updateButtons(); } }); if (findField.getEditor().getEditorComponent() instanceof JTextField) { ((JTextField) findField.getEditor().getEditorComponent()).addActionListener(this); } } findField.addActionListener(this); gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1; gbc.gridx = 1; gbc.insets = new Insets(5, 5, 5, 5); add(findField, gbc); JLabel replace = new JLabel(); replace.setText(FlexoLocalization.localizedForKey("replace_with")); gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0; gbc.gridx = 0; gbc.gridy = 1; gbc.insets = new Insets(0, 5, 5, 5); add(replace, gbc); replaceField = new JComboBox(new DefaultComboBoxModel(new Vector<String>())); replaceField.setEditable(true); gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1; gbc.gridx = 1; gbc.insets = new Insets(0, 5, 5, 5); add(replaceField, gbc); } @Override public void actionPerformed(ActionEvent e) { if (e.getSource() == findField) { String s = (String) findField.getEditor().getItem(); int index = ((DefaultComboBoxModel) findField.getModel()).getIndexOf(s); if (index == -1) { ((DefaultComboBoxModel) findField.getModel()).insertElementAt(s, 0); if (((DefaultComboBoxModel) findField.getModel()).getSize() > 15) { ((DefaultComboBoxModel) findField.getModel()).removeElementAt(((DefaultComboBoxModel) findField.getModel()) .getSize() - 1); } findField.setSelectedIndex(0); findField.revalidate(); } else { if (index != 0) { ((DefaultComboBoxModel) findField.getModel()).removeElementAt(index); ((DefaultComboBoxModel) findField.getModel()).insertElementAt(s, 0); findField.setSelectedIndex(0); findField.revalidate(); } } updateQuery(); } else if (e.getSource() == replaceField) { String s = (String) replaceField.getEditor().getItem(); int index = ((DefaultComboBoxModel) replaceField.getModel()).getIndexOf(s); if (index == -1) { ((DefaultComboBoxModel) replaceField.getModel()).insertElementAt(s, 0); if (((DefaultComboBoxModel) replaceField.getModel()).getSize() > 15) { ((DefaultComboBoxModel) replaceField.getModel()).removeElementAt(((DefaultComboBoxModel) replaceField.getModel()) .getSize() - 1); } replaceField.setSelectedIndex(0); replaceField.revalidate(); } else { if (index != 0) { ((DefaultComboBoxModel) replaceField.getModel()).removeElementAt(index); ((DefaultComboBoxModel) replaceField.getModel()).insertElementAt(s, 0); replaceField.setSelectedIndex(0); replaceField.revalidate(); } } } else if (e.getSource() == findField.getEditor().getEditorComponent()) { updateResults(); gotoNextResult(); } return; } } public class DirectionPanel extends JPanel implements ActionListener { protected JRadioButton forward; protected JRadioButton backward; public DirectionPanel() { super(new GridBagLayout()); setBorder(BorderFactory.createTitledBorder(FlexoLocalization.localizedForKey("direction"))); GridBagConstraints gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.WEST; gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 1; gbc.weighty = 0; forward = new JRadioButton(FlexoLocalization.localizedForKey("forward")); forward.setSelected(true); forward.addActionListener(this); backward = new JRadioButton(FlexoLocalization.localizedForKey("backward")); backward.addActionListener(this); add(forward, gbc); gbc.gridy = 1; add(backward, gbc); ButtonGroup group = new ButtonGroup(); group.add(forward); group.add(backward); } @Override public void actionPerformed(ActionEvent e) { updateResultNavigator(false); } } public class ScopePanel extends JPanel implements ActionListener { protected JRadioButton all; protected JRadioButton selectedText; public ScopePanel() { super(new GridBagLayout()); setBorder(BorderFactory.createTitledBorder(FlexoLocalization.localizedForKey("scope"))); GridBagConstraints gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.WEST; gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 1; gbc.weighty = 0; all = new JRadioButton(FlexoLocalization.localizedForKey("all")); all.setSelected(true); selectedText = new JRadioButton(FlexoLocalization.localizedForKey("selected_text")); all.addActionListener(this); selectedText.addActionListener(this); add(all, gbc); gbc.gridy = 1; add(selectedText, gbc); ButtonGroup group = new ButtonGroup(); group.add(all); group.add(selectedText); } @Override public void actionPerformed(ActionEvent e) { updateResultNavigator(false); } } public class OptionPanel extends JPanel implements ActionListener { protected JCheckBox caseSensitive; protected JCheckBox wrapSearch; protected JCheckBox wholeWord; protected JCheckBox regularExpression; public OptionPanel() { super(new GridBagLayout()); setBorder(BorderFactory.createTitledBorder(FlexoLocalization.localizedForKey("options"))); caseSensitive = new JCheckBox(FlexoLocalization.localizedForKey("case_sensitive")); wrapSearch = new JCheckBox(FlexoLocalization.localizedForKey("wrap_search")); wrapSearch.setSelected(true); wholeWord = new JCheckBox(FlexoLocalization.localizedForKey("whole_word")); regularExpression = new JCheckBox(FlexoLocalization.localizedForKey("regular_expression")); caseSensitive.addActionListener(this); wrapSearch.addActionListener(this); wholeWord.addActionListener(this); regularExpression.addActionListener(this); GridBagConstraints gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.WEST; gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 0; gbc.weighty = 0; add(caseSensitive, gbc); gbc.gridx = 1; gbc.weightx = 1; add(wrapSearch, gbc); gbc.weightx = 0; gbc.gridx = 0; gbc.gridy = 1; add(wholeWord, gbc); gbc.weightx = 1; gbc.gridx = 1; add(regularExpression, gbc); } @Override public void actionPerformed(ActionEvent e) { updateQuery(); } } public class BottomPanel extends JPanel implements ActionListener { protected JLabel statusText; protected JButton find; protected JButton replace; protected JButton replaceFind; protected JButton replaceAll; protected JButton close; public BottomPanel() { super(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.SOUTHWEST; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.gridx = 0; gbc.gridy = 2; gbc.weightx = 1; gbc.weighty = 1; gbc.gridwidth = 2; gbc.gridheight = 1; statusText = new JLabel(); add(statusText, gbc); find = new JButton(FlexoLocalization.localizedForKey("find")); find.addActionListener(this); gbc.anchor = GridBagConstraints.WEST; gbc.weightx = 0; gbc.weighty = 0; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.gridx = 1; gbc.gridy = 0; add(find, gbc); replace = new JButton(FlexoLocalization.localizedForKey("replace")); replace.addActionListener(this); gbc.gridy = 1; add(replace, gbc); replaceFind = new JButton(FlexoLocalization.localizedForKey("replace/find")); replaceFind.addActionListener(this); gbc.gridx = 2; gbc.gridy = 0; add(replaceFind, gbc); replaceAll = new JButton(FlexoLocalization.localizedForKey("replace_all")); replaceAll.addActionListener(this); gbc.gridy = 1; add(replaceAll, gbc); close = new JButton(FlexoLocalization.localizedForKey("close")); close.addActionListener(this); gbc.insets = new Insets(5, 0, 0, 0); gbc.gridy = 2; gbc.anchor = GridBagConstraints.EAST; add(close, gbc); Component glue = Box.createGlue(); gbc.insets = new Insets(0, 0, 0, 0); gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 1; gbc.weighty = 1; gbc.gridheight = 2; gbc.fill = GridBagConstraints.BOTH; add(glue, gbc); } public void setStatusText(String text) { statusText.setText("<html>" + text + "</html>"); } @Override public void actionPerformed(ActionEvent e) { if (e.getSource() == find) { gotoNextResult(); } else if (e.getSource() == close) { Window w = SwingUtilities.getWindowAncestor(this); if (w != null) { w.setVisible(false); } } else { if (resultNavigator != null) { if ((e.getSource() == replace || e.getSource() == replaceFind) && resultNavigator.getCurrentResult() != null) { if (textComponent.getSelectionStart() == resultNavigator.getCurrentResult().startOffset && textComponent.getSelectionEnd() == resultNavigator.getCurrentResult().endOffset) { resultNavigator.replaceBy(getReplacementText()); updateResults(); updateButtons(); if (e.getSource() == replaceFind) { gotoNextResult(); } } else { gotoNextResult(); } } else if (e.getSource() == replaceAll && resultNavigator != null) { if (resultNeedsUpdate) { updateResults(); } resultNavigator.replaceAllBy(getReplacementText()); updateResults(); } } } } } private FindReplaceFieldPanel findReplacePanel; private DirectionPanel directionPanel; private ScopePanel scopePanel; private OptionPanel optionPanel; private BottomPanel bottomPanel; protected ITextComponent textComponent; protected TextQuery query; protected TextQueryResult queryResult; protected TextQueryResult.ResultNavigator resultNavigator; public TextSearchPanel() { super(new BorderLayout()); this.query = new TextQuery(); initUI(); updateQuery(); } public ITextComponent getTextComponent() { return textComponent; } public void setTextComponent(ITextComponent textComponent) { if (this.textComponent == textComponent) { return; } if (this.textComponent != null) { this.textComponent.removeCaretListener(this); queryResult = null; } this.textComponent = textComponent; if (this.textComponent != null) { this.textComponent.addCaretListener(this); } resultNeedsUpdate = true; updateFields(); updateButtons(); updateScopePanel(); } public void notifyEditablePropertyChange() { updateFields(); updateButtons(); } private void updateFields() { if (textComponent != null) { findReplacePanel.findField.setEnabled(true); if (textComponent.getSelectedText() != null && textComponent.getSelectedText().length() != 0) { findReplacePanel.findField.getEditor().setItem(textComponent.getSelectedText()); } if (textComponent.isEditable()) { findReplacePanel.replaceField.setEnabled(true); } else { findReplacePanel.replaceField.setEnabled(false); } } else { findReplacePanel.findField.setEnabled(false); findReplacePanel.replaceField.setEnabled(false); } } @Override public Dimension getMinimumSize() { return getPreferredSize(); } private void initUI() { JPanel northPanel = new JPanel(new BorderLayout()); findReplacePanel = new FindReplaceFieldPanel(); northPanel.add(findReplacePanel, BorderLayout.NORTH); JPanel centerPanel = new JPanel(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 0.5; gbc.weighty = 0; directionPanel = new DirectionPanel(); centerPanel.add(directionPanel, gbc); scopePanel = new ScopePanel(); gbc.gridx = 1; centerPanel.add(scopePanel, gbc); northPanel.add(centerPanel, BorderLayout.CENTER); optionPanel = new OptionPanel(); northPanel.add(optionPanel, BorderLayout.SOUTH); add(northPanel, BorderLayout.NORTH); bottomPanel = new BottomPanel(); add(bottomPanel, BorderLayout.SOUTH); setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); } protected boolean resultNeedsUpdate = true; protected void gotoNextResult() { if (resultNeedsUpdate) { updateResults(); } try { textComponent.removeCaretListener(this); bottomPanel.setStatusText(""); Result r = resultNavigator.getNextResult(); textComponent.select(r.startOffset, r.endOffset); } catch (EndOfDocumentHasBeenReachedException e) { bottomPanel.setStatusText(FlexoLocalization.localizedForKey("end_of_document_has_been_reached")); } catch (ResultNotFoundException e) { bottomPanel.setStatusText(FlexoLocalization.localizedForKey("no_result_found")); } finally { textComponent.addCaretListener(this); } } protected void updateResults() { queryResult = TextQueryEngine.performSearchOnDocument(textComponent.getDocument(), query); resultNavigator = queryResult.new ResultNavigator(); updateButtons(); updateResultNavigator(true); resultNeedsUpdate = false; } protected void updateQuery() { query.setCaseSensitive(optionPanel.caseSensitive.isSelected()); query.setRegularExpression(optionPanel.regularExpression.isSelected()); query.setSearchedText((String) findReplacePanel.findField.getSelectedItem()); query.setWholeWord(optionPanel.wholeWord.isSelected()); resultNeedsUpdate = true; } protected void updateResultNavigator(boolean notifyCaretPosition) { if (resultNavigator == null) { return; } resultNavigator.setDirection(directionPanel.forward.isSelected() ? Direction.FORWARD : Direction.BACKWARD); resultNavigator.setLimitToSelectedText(scopePanel.selectedText.isSelected()); if (textComponent.getSelectionStart() > textComponent.getSelectionEnd()) { resultNavigator.setSelectionStart(textComponent.getSelectionEnd()); resultNavigator.setSelectionEnd(textComponent.getSelectionStart()); } else { resultNavigator.setSelectionStart(textComponent.getSelectionStart()); resultNavigator.setSelectionEnd(textComponent.getSelectionEnd()); } resultNavigator.setWrapSearch(optionPanel.wrapSearch.isSelected()); if (notifyCaretPosition) { resultNavigator.setCurrentCaretPosition(textComponent.getCaretPosition()); } } protected void updateButtons() { if (textComponent == null) { bottomPanel.find.setEnabled(false); bottomPanel.replace.setEnabled(false); bottomPanel.replaceFind.setEnabled(false); bottomPanel.replaceAll.setEnabled(false); } else { bottomPanel.find.setEnabled(getSearchedText().length() != 0); if (textComponent.isEditable()) { bottomPanel.replace.setEnabled(true && resultNavigator != null); bottomPanel.replaceFind.setEnabled(true && resultNavigator != null); bottomPanel.replaceAll.setEnabled(true); } else { bottomPanel.replace.setEnabled(false); bottomPanel.replaceFind.setEnabled(false); bottomPanel.replaceAll.setEnabled(false); } } } @Override public void caretUpdate(CaretEvent e) { updateResultNavigator(true); updateScopePanel(); if (textComponent.getSelectionStart() > -1 && textComponent.getSelectionEnd() > -1) { if (resultNavigator == null || resultNavigator.getCurrentResult() == null || resultNavigator.getCurrentResult().startOffset != textComponent.getSelectionStart() || resultNavigator.getCurrentResult().endOffset != textComponent.getSelectionEnd()) { updateResultNavigator(true); } } } private void updateScopePanel() { scopePanel.selectedText.setEnabled(textComponent.getSelectedText() != null && textComponent.getSelectedText().length() != 0); if (!scopePanel.selectedText.isEnabled()) { scopePanel.all.setSelected(true); } } public JButton getDefaultButton() { return bottomPanel.find; } public void setSearchedText(String selectedText) { findReplacePanel.findField.getEditor().setItem(selectedText); } protected String getReplacementText() { if (findReplacePanel.replaceField.getEditor().getItem() == null) { return ""; } return (String) findReplacePanel.replaceField.getEditor().getItem(); } protected String getSearchedText() { if (findReplacePanel.findField.getEditor().getItem() == null) { return ""; } return (String) findReplacePanel.findField.getEditor().getItem(); } public void onFocus() { findReplacePanel.findField.getEditor().selectAll(); } }