blob: a6c0e8db999088a4d3e416cd63e964e3f4ac3f39 [file] [log] [blame]
/*
* 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 org.netbeans.modules.properties;
import java.awt.event.*;
import java.awt.Rectangle;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.ref.SoftReference;
import java.util.HashSet;
import java.util.Set;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import org.openide.DialogDescriptor;
import org.openide.DialogDisplayer;
import org.openide.util.NbBundle;
import org.openide.util.WeakListeners;
/**
* FindPerformer is a performer of the FindAction which is invoked on Resource Bundles table view component.
* Does actual dirty job, search on the actual activated table and sets the results as highlighted text on particular cell.
*
* @author Peter Zavadsky
* @author Marian Petras
*/
public class FindPerformer extends javax.swing.AbstractAction
implements PropertyChangeListener {
/** Table on which perform the search. */
private JTable table;
/** String to find. */
private String findString;
/** Stores values which are used to start search from and store the results for next search.
* 1st item - row index of cell with found string,
* 2nd item - column index of cell with found string,
* 3rd item - start offset of found string.
* 4th item - end offset of found string.
*/
private int[] searchValues;
/** Flag if it is set highliht search. */
private boolean highlightSearch = true;
/** Flag if it is set match case search. */
private boolean matchCaseSearch = false;
/** Flag if it is set forward search. */
private boolean backwardSearch = false;
/** Flag if it is set wrap search. */
private boolean wrapSearch = true;
/** Flag if it is set search by rows. */
private boolean rowSearch = true;
/** Listener for registering keystrokes to find next action. */
private final ActionListener findNextActionListener;
/** Listener for registering keystrokes to find previous action. */
private final ActionListener findPreviousActionListener;
/** Listener for registering keystrokes to toggle highlight action. */
private final ActionListener toggleHighlightListener;
/** Keeps history of found strings. */
private Set<String> history = new HashSet<String>();
/** Helper variable keeping <code>settings</code>. */
private TableViewSettings settings;
// Initializes action listener use to register for key strokes to table.
{
findNextActionListener = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
// Find next action invoked (default F3) -> search next occurence.
if(searchValues != null) {
synchronized(this) {
backwardSearch = false;
performSearch();
}
}
}
};
findPreviousActionListener = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
// Find previous action invoked (default Shift-F3)-> search previous occurence.
if(searchValues != null) {
synchronized(this) {
backwardSearch = true;
performSearch();
}
}
}
};
toggleHighlightListener = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
// Toggle highlight action invoked (defauilt Shift-Alt-H) -> toggle highlight.
highlightSearch = !highlightSearch;
table.repaint();
}
};
} // end of initializer
/** Soft reference for caching singleton find performer on last table view. */
private static SoftReference<FindPerformer> softRef;
/** Dialog for perform search. */
private static JDialog findDialog;
/** Name of client property used to store search result values. */
public static final String TABLE_SEARCH_RESULT = "table.search.result"; // NOI18N
/** Creates new FindPerformer. Used internally only, for getting the singleton use #getFindPerformer method. */
private FindPerformer(JTable table) {
this.table = table;
settings = TableViewSettings.getDefault();
settings.addPropertyChangeListener(
WeakListeners.propertyChange(this, settings)
);
registerKeyStrokes();
}
/**
* Listen on setting changes.
*/
public void propertyChange(PropertyChangeEvent evt) {
// Settings were changed reset registered key strokes.
registerKeyStrokes();
}
/** Gets find performer. */
public static FindPerformer getFindPerformer(JTable table) {
if(softRef != null) {
FindPerformer fp = softRef.get();
if(fp != null) {
if(!fp.validateTable(table)) {
fp.resetTable(table);
fp.registerKeyStrokes();
}
return fp;
}
}
FindPerformer fp = new FindPerformer(table);
softRef = new SoftReference<FindPerformer>(fp);
return fp;
}
/** Resets the table if necessary. */
private void resetTable(JTable table) {
this.table = table;
}
/** Validates if the table is the same one as last opened find panel. */
private boolean validateTable(JTable table) {
if(this.table != null && this.table.equals(table))
return true;
return false;
}
/** Register key strokes F3 and Shift-F3 (next & previous search) to table. */
private synchronized void registerKeyStrokes() {
// Register key strokes to table.
KeyStroke[] keyStrokes = settings.getKeyStrokesFindNext();
for(int i=0; i<keyStrokes.length; i++) {
table.registerKeyboardAction(
findNextActionListener,
keyStrokes[i],
JComponent.WHEN_IN_FOCUSED_WINDOW
);
}
keyStrokes = settings.getKeyStrokesFindPrevious();
for(int i=0; i<keyStrokes.length; i++) {
table.registerKeyboardAction(
findPreviousActionListener,
keyStrokes[i],
JComponent.WHEN_IN_FOCUSED_WINDOW
);
}
keyStrokes = settings.getKeyStrokesToggleHighlight();
for(int i=0; i<keyStrokes.length; i++) {
table.registerKeyboardAction(
toggleHighlightListener,
keyStrokes[i],
JComponent.WHEN_IN_FOCUSED_WINDOW
);
}
}
/** Getter for find string. */
public String getFindString() {
return findString;
}
/** Getter for highlight search flag. */
public boolean isHighlightSearch() {
return highlightSearch;
}
/* implements interface javax.swing.Action */
public void actionPerformed(java.awt.event.ActionEvent e) {
if(findDialog == null)
createFindDialog();
else {
// There is already the find dialog open.
findDialog.setVisible(true);
findDialog.requestFocusInWindow();
}
}
/** Methods which does the dirty job. It creates and shows find dialog. */
private void createFindDialog() {
final JDialog dialog;
final FindPanel panel = new FindPanel();
DialogDescriptor dd = new DialogDescriptor(
panel,
NbBundle.getBundle(FindPerformer.class).getString("LBL_Title"), // title
false, // modal
new JButton[0], // empty options, we provide our owns
null, // initvalue
DialogDescriptor.DEFAULT_ALIGN,
null, // helpCtx
null // listener
);
dialog = (javax.swing.JDialog)DialogDisplayer.getDefault().createDialog(dd);
// Static reference to the dialog.
findDialog = dialog;
// set findButton as default
dialog.getRootPane().setDefaultButton(panel.getButtons()[0]);
dialog.setFocusable(false);
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
// set listeners
// find button
panel.getButtons()[0].addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent evt) {
findString = ((JTextField)panel.getComboBox().getEditor().getEditorComponent()).getText();
// Set flags.
highlightSearch = panel.getHighlightCheck().isSelected();
matchCaseSearch = panel.getMatchCaseCheck().isSelected();
backwardSearch = panel.getBackwardCheck().isSelected();
wrapSearch = panel.getWrapCheck().isSelected();
rowSearch = panel.getRowCheck().isSelected();
dialog.dispose();
if(findString != null && !findString.trim().equals("")) {
history.add(findString.intern());
performSearch();
}
// For case previous search results need to be erased.
table.repaint();
}
}
);
// cancel button
panel.getButtons()[1].addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent evt) {
dialog.dispose();
}
}
);
dialog.addWindowListener(new WindowAdapter() {
public void windowClosed(WindowEvent e) {
findDialog = null;
}
});
// Set flags.
panel.getHighlightCheck().setSelected(highlightSearch);
panel.getMatchCaseCheck().setSelected(matchCaseSearch);
panel.getBackwardCheck().setSelected(backwardSearch);
panel.getWrapCheck().setSelected(wrapSearch);
panel.getRowCheck().setSelected(rowSearch);
// Set combo box list items.
panel.getComboBox().setModel(new DefaultComboBoxModel(history.toArray()));
// Set last found string as selected if exist.
if (findString != null) {
panel.getComboBox().setSelectedItem(findString);
panel.getComboBox().getEditor().selectAll();
}
dialog.setVisible(true);
panel.requestFocusInWindow();
}
/** Closes find dialog if one is opened. */
public void closeFindDialog() {
if(findDialog != null) {
synchronized(findDialog) {
findDialog.setVisible(false);
findDialog.dispose();
findDialog = null;
}
}
}
/** Prepares searchValues before search. The search will start
* from selected cell or from beginning by forward or
* from end by backward search respectivelly.*/
private void prepareSearch() {
int row = table.getSelectedRow();
int column = table.getSelectedColumn();
// Is selected the cell found by previous search.
if(searchValues != null && row == searchValues[0] && column == searchValues[1])
return;
if(row != -1 && column != -1) {
// Some cell is selected.
int startOffset, endOffset;
startOffset = endOffset = ((JTextField)((PropertiesTableCellEditor)table.getCellEditor(row, column)).getComponent()).getCaretPosition();
searchValues = new int[] {row, column, startOffset, endOffset};
} else {
// Nothing is selected, set first (last) cell by forward (backward) search.
if(backwardSearch) {
int lastRow = table.getRowCount()-1;
int lastColumn = table.getColumnCount()-1;
int startOffset, endOffset;
startOffset = endOffset = ((PropertiesTableModel.StringPair)table.getValueAt(lastRow, lastColumn)).getValue().length()-1;
searchValues = new int[] {lastRow, lastColumn, startOffset, endOffset};
} else {
searchValues = new int[] {0, 0, 0, 0};
}
}
}
/** Perform search, store results and set editable cell if search was successful. */
private synchronized void performSearch() {
prepareSearch();
// perform search not in AWT-thread
PropertiesRequestProcessor.getInstance().post(
new Runnable() {
public void run() {
// Do wrap search?
boolean wrap = false;
do {
final int[] result = search(searchValues[0], searchValues[1], backwardSearch ? searchValues[2]-1 : searchValues[3]);
if(wrapSearch && !wrap && result == null) {
// Do wrapping.
// Wrap search if was found something in second to last search.
if(backwardSearch) {
int lastRow = table.getRowCount()-1;
int lastColumn = table.getColumnCount()-1;
searchValues = new int[] {lastRow,
lastColumn,
((PropertiesTableModel.StringPair)table.getValueAt(lastRow, lastColumn)).getValue().length()-1,
0};
} else {
searchValues = new int[] {0, 0, 0, 0};
}
wrap = true;
} else {
if(result != null) {
// store new search results
searchValues = result;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// autoscroll to cell if possible and necessary
if (table.getAutoscrolls()) {
Rectangle cellRect = table.getCellRect(result[0], result[1], false);
if (cellRect != null) {
table.scrollRectToVisible(cellRect);
}
}
// update selection & edit
if (table.isEditing()) {
/*
* If the cell at (result[1], result[0]) is currently being edited,
* method setSelectionInterval does not finish the current editing
* session and the selection is not changed. But the result
* of calling setSelectionInterval(...) is that method
* BundleEditPanel.updateSelection(...) is called which sets value
* and comment from the cell at (result[1], result[0]).
* The consequence of these steps is that values from
* cell (result[1], result[0]) are set to the cell currently being
* edited - see bug #81934
* (http://www.netbeans.org/issues/show_bug.cgi?id=81934).
*/
table.getCellEditor().stopCellEditing();
}
table.getColumnModel().getSelectionModel().setSelectionInterval(result[1], result[1]);
table.getSelectionModel().setSelectionInterval(result[0], result[0]);
// set editable cell
table.editCellAt(result[0], result[1]);
}
});
}
// Store new search results.
searchValues = result;
wrap = false;
}
} while (wrap);
} // end of run method
}
);
}
/** Perform actual search on table started from the cell specified.
* @param startColumn Ccolumn index of cell to start the search.
* @param startRow Row index of cell to start the search.
* @param startOffset Offset from start the search.
* @return srray of integers where first is column index, second row index, third offset,
* in case no thing was found null is returned */
private int[] search(int startRow, int startColumn, int startOffset) {
// Helper for reseting startOffset after first iteration.
boolean firstIteration = true;
// If rowSearch->row loop else->column loop.
for(int i= rowSearch ? startRow : startColumn;
backwardSearch ? i>=0 : i<(rowSearch ? table.getRowCount() : table.getColumnCount());
i = backwardSearch ? i-1 : i+1 ) {
// If rowSearch->column loop else->row loop.
for(int j= rowSearch ? startColumn : startRow;
backwardSearch ? j>=0 : j<(rowSearch ? table.getColumnCount() : table.getRowCount());
j = backwardSearch ? j-1 : j+1) {
// Set row and column indexes for this iteration.
int row = rowSearch ? i : j;
int column = rowSearch ? j : i;
String str = ((PropertiesTableModel.StringPair)table.getValueAt(row, column)).toString();
// Skip to next iteration if value is null or is the string in cell is shorter than string to find.
if(str == null || str.length() < findString.length())
continue;
if(!firstIteration)
startOffset = backwardSearch ? str.length()-findString.length() : 0;
int offset = containsFindString(str, startOffset);
if(offset>=0) {
// puts client property which is then used by cell editor
// for setting and highlighting the find string
table.putClientProperty(TABLE_SEARCH_RESULT, new int[] {row, column, offset, offset+findString.length()});
return new int[] {row, column, offset, offset+findString.length()};
}
if(firstIteration) firstIteration = false;
}
// Next inner loop from beginning(end) for forward (backward) search.
if(rowSearch)
startColumn = backwardSearch ? table.getColumnCount()-1 : 0;
else
startRow = backwardSearch ? table.getRowCount()-1 : 0;
}
return null;
}
/** The function search if findString occures whitin specified string.
* @param str String which is looked if contains find string.
* @param startOffset Offset from starts the search.
* @return Offset on which starts find string whitin str or -1. */
private int containsFindString(String str, int startOffset) {
if(startOffset < 0 || startOffset >= str.length())
return -1;
for(int i=startOffset;
backwardSearch ? i>=0 : i<(str.length()-findString.length()+1);
i = backwardSearch ? i-1 : i+1) {
if(findString.regionMatches(!matchCaseSearch, 0, str, i, findString.length()))
return i;
}
return -1;
}
}