| /* |
| * 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; |
| } |
| |
| } |