--- a/openide.explorer/apichanges.xml +++ a/openide.explorer/apichanges.xml @@ -50,6 +50,20 @@ Explorer API + + + QuickSearch attached to OutlineView + + + + + + Added OutlineView.getQuickSearch() + method to get and control quick search functionality on OutlineView. + + + + API method for creating a PropertyEnv instance --- a/openide.explorer/manifest.mf +++ a/openide.explorer/manifest.mf @@ -2,5 +2,5 @@ OpenIDE-Module: org.openide.explorer OpenIDE-Module-Localizing-Bundle: org/openide/explorer/Bundle.properties AutoUpdate-Essential-Module: true -OpenIDE-Module-Specification-Version: 6.42 +OpenIDE-Module-Specification-Version: 6.43 --- a/openide.explorer/nbproject/project.xml +++ a/openide.explorer/nbproject/project.xml @@ -50,6 +50,14 @@ org.openide.explorer + org.netbeans.spi.quicksearch + + + + 1.14 + + + org.netbeans.swing.outline --- a/openide.explorer/src/org/openide/explorer/view/OutlineView.java +++ a/openide.explorer/src/org/openide/explorer/view/OutlineView.java @@ -120,6 +120,7 @@ import javax.swing.table.TableModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; +import org.netbeans.api.quicksearch.QuickSearch; import org.netbeans.modules.openide.explorer.ExplorerActionsImpl; import org.netbeans.swing.etable.ETable; import org.netbeans.swing.etable.ETableColumnModel; @@ -681,24 +682,14 @@ } /** - * Test whether the quick search feature is enabled or not. - * Default is enabled (true). - * @since - * @return true if quick search feature is enabled, false otherwise. + * Get the quick search support, that is attached to this view. + * @return The quick search support. + * @since 6.43 */ - /*public*/ boolean isQuickSearchAllowed() { - return quickSearch.isEnabled(); + public final QuickSearch getQuickSearch() { + return quickSearch; } - /** - * Set whether the quick search feature is enabled or not. - * @since - * @param allowedQuickSearch true if quick search shall be enabled - */ - /*public*/ void setQuickSearchAllowed(boolean allowedQuickSearch) { - quickSearch.setEnabled(allowedQuickSearch); - } - /** Initializes the component and lookup explorer manager. */ @Override --- a/openide.explorer/src/org/openide/explorer/view/QuickSearch.java +++ a/openide.explorer/src/org/openide/explorer/view/QuickSearch.java @@ -1,514 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2012 Oracle and/or its affiliates. All rights reserved. - * - * Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners. - * - * The contents of this file are subject to the terms of either the GNU - * General Public License Version 2 only ("GPL") or the Common - * Development and Distribution License("CDDL") (collectively, the - * "License"). You may not use this file except in compliance with the - * License. You can obtain a copy of the License at - * http://www.netbeans.org/cddl-gplv2.html - * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the - * specific language governing permissions and limitations under the - * License. When distributing the software, include this License Header - * Notice in each file and include the License file at - * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the GPL Version 2 section of the License file that - * accompanied this code. If applicable, add the following below the - * License Header, with the fields enclosed by brackets [] replaced by - * your own identifying information: - * "Portions Copyrighted [year] [name of copyright owner]" - * - * If you wish your version of this file to be governed by only the CDDL - * or only the GPL Version 2, indicate your decision by adding - * "[Contributor] elects to include this software in this distribution - * under the [CDDL or GPL Version 2] license." If you do not indicate a - * single choice of license, a recipient has the option to distribute - * your version of this file under either the CDDL, the GPL Version 2 or - * to extend the choice of license to its licensees as provided above. - * However, if you add GPL Version 2 code and therefore, elected the GPL - * Version 2 license, then the option applies only if the new code is - * made subject to such option by the copyright holder. - * - * Contributor(s): - * - * Portions Copyrighted 2012 Sun Microsystems, Inc. - */ -package org.openide.explorer.view; - -import java.awt.*; -import java.awt.event.*; -import java.lang.ref.WeakReference; -import java.util.LinkedList; -import java.util.List; -import javax.swing.*; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; -import javax.swing.text.Position.Bias; - -/** - * Quick search infrastructure - * - * @author Martin Entlicher - */ -class QuickSearch { - - private static final String ICON_FIND = "org/openide/explorer/view/find.png"; - private static final String ICON_FIND_WITH_MENU = "org/openide/explorer/view/findMenu.png"; - - private final JComponent component; - private final Object constraints; - private boolean enabled = true; - private final List listeners = new LinkedList(); - private SearchTextField searchTextField; - private KeyAdapter quickSearchKeyAdapter; - private JPanel searchPanel; - private JMenu popupMenu; - - private QuickSearch(JComponent component, Object constraints) { - this.component = component; - this.constraints = constraints; - setUpSearch(); - } - - public static QuickSearch attach(JComponent component, Object constraints) { - Object qso = component.getClientProperty(QuickSearch.class.getName()); - if (qso instanceof QuickSearch) { - return (QuickSearch) qso; - } else { - QuickSearch qs = new QuickSearch(component, constraints); - component.putClientProperty(QuickSearch.class.getName(), qs); - return qs; - } - } - - public void detach() { - setEnabled(false); - component.putClientProperty(QuickSearch.class.getName(), null); - } - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - if (this.enabled == enabled) { - return ; - } - this.enabled = enabled; - if (enabled) { - component.addKeyListener(quickSearchKeyAdapter); - } else { - component.removeKeyListener(quickSearchKeyAdapter); - } - } - - public void addQuickSearchListener(QuickSearchListener qsl) { - synchronized (listeners) { - listeners.add(qsl); - } - } - - public void removeQuickSearchListener(QuickSearchListener qsl) { - synchronized (listeners) { - listeners.remove(qsl); - } - } - - public void setPopupMenu(JMenu popupMenu) { - this.popupMenu = popupMenu; - } - - public void processKeyEvent(KeyEvent ke) { - switch(ke.getID()) { - case KeyEvent.KEY_PRESSED: - quickSearchKeyAdapter.keyPressed(ke); - break; - case KeyEvent.KEY_RELEASED: - quickSearchKeyAdapter.keyReleased(ke); - break; - case KeyEvent.KEY_TYPED: - quickSearchKeyAdapter.keyTyped(ke); - break; - } - } - - private QuickSearchListener[] getQuickSearchListeners() { - QuickSearchListener[] qsls; - synchronized (listeners) { - qsls = listeners.toArray(new QuickSearchListener[] {}); - } - return qsls; - } - - private void fireQuickSearchUpdate(String searchText) { - for (QuickSearchListener qsl : getQuickSearchListeners()) { - qsl.quickSearchUpdate(searchText); - } - } - - private void fireShowNextSelection(Bias bias) { - for (QuickSearchListener qsl : getQuickSearchListeners()) { - qsl.showNextSelection(bias); - } - } - - private String findMaxPrefix(String prefix) { - for (QuickSearchListener qsl : getQuickSearchListeners()) { - prefix = qsl.findMaxPrefix(prefix); - } - return prefix; - } - - private void fireQuickSearchConfirmed() { - for (QuickSearchListener qsl : getQuickSearchListeners()) { - qsl.quickSearchConfirmed(); - } - } - - private void fireQuickSearchCanceled() { - for (QuickSearchListener qsl : getQuickSearchListeners()) { - qsl.quickSearchCanceled(); - } - } - - private void setUpSearch() { - searchTextField = new SearchTextField(); - // create new key listeners - quickSearchKeyAdapter = ( - new KeyAdapter() { - @Override - public void keyTyped(KeyEvent e) { - int modifiers = e.getModifiers(); - int keyCode = e.getKeyCode(); - char c = e.getKeyChar(); - - //#43617 - don't eat + and - - //#98634 - and all its duplicates dont't react to space - if ((c == '+') || (c == '-') || (c==' ')) return; // NOI18N - - if (((modifiers > 0) && (modifiers != KeyEvent.SHIFT_MASK)) || e.isActionKey()) { - return; - } - - if (Character.isISOControl(c) || - (keyCode == KeyEvent.VK_SHIFT) || - (keyCode == KeyEvent.VK_ESCAPE)) return; - - final KeyStroke stroke = KeyStroke.getKeyStrokeForEvent(e); - searchTextField.setText(String.valueOf(stroke.getKeyChar())); - - displaySearchField(); - e.consume(); - } - } - ); - if(isEnabled()){ - component.addKeyListener(quickSearchKeyAdapter); - } - // Create a the "multi-event" listener for the text field. Instead of - // adding separate instances of each needed listener, we're using a - // class which implements them all. This approach is used in order - // to avoid the creation of 4 instances which takes some time - SearchFieldListener searchFieldListener = new SearchFieldListener(); - searchTextField.addKeyListener(searchFieldListener); - searchTextField.addFocusListener(searchFieldListener); - searchTextField.getDocument().addDocumentListener(searchFieldListener); - - } - - private void displaySearchField() { - if (searchPanel != null || !isEnabled()) { - return; - } - /* - TreeView previousSearchField = lastSearchField.get(); - if (previousSearchField != null && previousSearchField != this) { - previousSearchField.removeSearchField(); - } - */ - //JViewport vp = getViewport(); - //originalScrollMode = vp.getScrollMode(); - //vp.setScrollMode(JViewport.SIMPLE_SCROLL_MODE); - searchTextField.setOriginalFocusOwner(); - searchTextField.setFont(component.getFont()); - searchPanel = new SearchPanel(); - //JLabel lbl = new JLabel(NbBundle.getMessage(TreeView.class, "LBL_QUICKSEARCH")); //NOI18N - final JLabel lbl; - if (popupMenu != null) { - lbl = new JLabel(org.openide.util.ImageUtilities.loadImageIcon(ICON_FIND_WITH_MENU, false)); - lbl.addMouseListener(new MouseAdapter() { - @Override - public void mousePressed(MouseEvent e) { - if (e != null && !SwingUtilities.isLeftMouseButton(e)) { - return; - } - JPopupMenu pm = popupMenu.getPopupMenu(); - pm.show(lbl, 0, lbl.getHeight() - 1); - } - }); - } else { - lbl = new JLabel(org.openide.util.ImageUtilities.loadImageIcon(ICON_FIND, false)); - } - searchPanel.setLayout(new BoxLayout(searchPanel, BoxLayout.X_AXIS)); - searchPanel.add(lbl); - searchPanel.add(searchTextField); - lbl.setLabelFor(searchTextField); - searchTextField.setColumns(10); - searchTextField.setMaximumSize(searchTextField.getPreferredSize()); - searchTextField.putClientProperty("JTextField.variant", "search"); //NOI18N - lbl.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5)); - //JToggleButton matchCaseButton = new JToggleButton("aA"); - //matchCaseButton.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5)); - //searchPanel.add(matchCaseButton); - if (component instanceof JScrollPane) { - // ((JScrollPane) component).getViewport().setScrollMode(JViewport.SIMPLE_SCROLL_MODE); - } - if (constraints == null) { - component.add(searchPanel); - } else { - component.add(searchPanel, constraints); - } - component.invalidate(); - component.revalidate(); - component.repaint(); - searchTextField.requestFocus(); - } - - private void removeSearchField() { - if (searchPanel == null) { - return; - } - component.remove(searchPanel); - searchPanel = null; - //getViewport().setScrollMode(originalScrollMode); - component.invalidate(); - component.revalidate(); - component.repaint(); - } - - public static String findMaxCommonSubstring(String str1, String str2, boolean ignoreCase) { - int n1 = str1.length(); - int n2 = str2.length(); - int i = 0; - if (ignoreCase) { - for ( ; i < n1 && i < n2; i++) { - char c1 = Character.toUpperCase(str1.charAt(i)); - char c2 = Character.toUpperCase(str2.charAt(i)); - if (c1 != c2) { - break; - } - } - } else { - for ( ; i < n1 && i < n2; i++) { - char c1 = str1.charAt(i); - char c2 = str2.charAt(i); - if (c1 != c2) { - break; - } - } - } - return str1.substring(0, i); - } - - public static interface QuickSearchListener { - - void quickSearchUpdate(String searchText); - - void showNextSelection(Bias bias); - - String findMaxPrefix(String prefix); - - void quickSearchConfirmed(); - - void quickSearchCanceled(); - - } - - private static class SearchPanel extends JPanel { - - public SearchPanel() { - if (ViewUtil.isAquaLaF) { - setBorder(BorderFactory.createEmptyBorder(9,6,8,2)); - } else { - setBorder(BorderFactory.createEmptyBorder(2,6,2,2)); - } - setOpaque(true); - } - - @Override - protected void paintComponent(Graphics g) { - if (ViewUtil.isAquaLaF && g instanceof Graphics2D) { - Graphics2D g2d = (Graphics2D) g; - g2d.setPaint(new GradientPaint(0, 0, UIManager.getColor("NbExplorerView.quicksearch.background.top"), - 0, getHeight(), UIManager.getColor("NbExplorerView.quicksearch.background.bottom")));//NOI18N - g2d.fillRect(0, 0, getWidth(), getHeight()); - g2d.setColor(UIManager.getColor("NbExplorerView.quicksearch.border")); //NOI18N - g2d.drawLine(0, 0, getWidth(), 0); - } else { - super.paintComponent(g); - } - } - } - - /** searchTextField manages focus because it handles VK_ESCAPE key */ - private class SearchTextField extends JTextField { - - private WeakReference originalFocusOwner = new WeakReference(null); - - public SearchTextField() { - } - - void setOriginalFocusOwner() { - Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); - if (focusOwner != null && component.isAncestorOf(focusOwner)) { - originalFocusOwner = new WeakReference(focusOwner); - } else { - originalFocusOwner = new WeakReference(component); - } - } - - void requestOriginalFocusOwner() { - SwingUtilities.invokeLater( - new Runnable() { - //additional bugfix - do focus change later or removing - //the component while it's focused will cause focus to - //get transferred to the next component in the - //parent focusTraversalPolicy *after* our request - //focus completes, so focus goes into a black hole - Tim - @Override - public void run() { - Component fo = originalFocusOwner.get(); - if (fo != null) { - fo.requestFocusInWindow(); - } - } - } - ); - } - - @Override - public boolean isManagingFocus() { - return true; - } - - @Override - public void processKeyEvent(KeyEvent ke) { - //override the default handling so that - //the parent will never receive the escape key and - //close a modal dialog - if (ke.getKeyCode() == KeyEvent.VK_ESCAPE) { - removeSearchField(); - ke.consume(); - // bugfix #32909, reqest focus when search field is removed - requestOriginalFocusOwner(); - fireQuickSearchCanceled(); - } else { - super.processKeyEvent(ke); - } - } - }; - - private class SearchFieldListener extends KeyAdapter implements DocumentListener, FocusListener { - - private boolean ignoreEvents; - - SearchFieldListener() { - } - - @Override - public void changedUpdate(DocumentEvent e) { - if (ignoreEvents) return; - searchForNode(); - } - - @Override - public void insertUpdate(DocumentEvent e) { - if (ignoreEvents) return; - searchForNode(); - } - - @Override - public void removeUpdate(DocumentEvent e) { - if (ignoreEvents) return; - searchForNode(); - } - - @Override - public void keyPressed(KeyEvent e) { - int keyCode = e.getKeyCode(); - - if (keyCode == KeyEvent.VK_ESCAPE) { - removeSearchField(); - searchTextField.requestOriginalFocusOwner(); - fireQuickSearchCanceled(); - e.consume(); - } else if (keyCode == KeyEvent.VK_UP || (keyCode == KeyEvent.VK_F3 && e.isShiftDown())) { - fireShowNextSelection(Bias.Backward); - // Stop processing the event here. Otherwise it's dispatched - // to the tree too (which scrolls) - e.consume(); - } else if (keyCode == KeyEvent.VK_DOWN || keyCode == KeyEvent.VK_F3) { - fireShowNextSelection(Bias.Forward); - // Stop processing the event here. Otherwise it's dispatched - // to the tree too (which scrolls) - e.consume(); - } else if (keyCode == KeyEvent.VK_TAB) { - String maxPrefix = findMaxPrefix(searchTextField.getText()); - ignoreEvents = true; - try { - searchTextField.setText(maxPrefix); - } finally { - ignoreEvents = false; - } - - e.consume(); - } else if (keyCode == KeyEvent.VK_ENTER) { - removeSearchField(); - fireQuickSearchConfirmed(); - - component.requestFocusInWindow(); - e.consume(); - } - } - - /** Searches for a node in the tree. */ - private void searchForNode() { - String text = searchTextField.getText(); - fireQuickSearchUpdate(text); - } - - @Override - public void focusGained(FocusEvent e) { - if (e.getSource() == searchTextField) { - // make sure nothing is selected - int n = searchTextField.getText().length(); - searchTextField.select(n, n); - } - } - - @Override - public void focusLost(FocusEvent e) { - if (e.isTemporary()) return ; - Component oppositeComponent = e.getOppositeComponent(); - if (e.getSource() != searchTextField) { - ((Component) e.getSource()).removeFocusListener(this); - } - if (oppositeComponent instanceof JMenuItem || oppositeComponent instanceof JPopupMenu) { - oppositeComponent.addFocusListener(this); - return ; - } - if (oppositeComponent == searchTextField) { - return ; - } - removeSearchField(); - fireQuickSearchConfirmed(); - } - } - -} --- a/openide.explorer/src/org/openide/explorer/view/TableQuickSearchSupport.java +++ a/openide.explorer/src/org/openide/explorer/view/TableQuickSearchSupport.java @@ -64,13 +64,15 @@ import javax.swing.text.Position; import org.openide.util.NbBundle; import org.openide.util.NbPreferences; +import org.netbeans.api.quicksearch.QuickSearch; +import org.netbeans.api.quicksearch.QuickSearchListener; /** * Quick search support for JTable * * @author Martin Entlicher */ -class TableQuickSearchSupport implements QuickSearch.QuickSearchListener { +class TableQuickSearchSupport implements QuickSearchListener { private int quickSearchInitialRow = -1; // The search was initiated here private int quickSearchInitialColumn = -1; // The search was initiated here --- a/openide.explorer/src/org/openide/explorer/view/TreeTableView.java +++ a/openide.explorer/src/org/openide/explorer/view/TreeTableView.java @@ -85,6 +85,8 @@ import org.openide.nodes.NodeReorderEvent; import org.openide.util.Utilities; +import org.netbeans.api.quicksearch.QuickSearch; + /** Explorer view. Allows to view tree of nodes on the left * and its properties in table on the right. --- a/openide.explorer/src/org/openide/explorer/view/TreeView.java +++ a/openide.explorer/src/org/openide/explorer/view/TreeView.java @@ -141,6 +141,9 @@ import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; +import org.netbeans.api.quicksearch.QuickSearch; +import org.netbeans.api.quicksearch.QuickSearchListener; +import org.openide.awt.Actions; /** @@ -224,8 +227,7 @@ transient private int allowedDropActions = DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_REFERENCE; /** Quick Search support */ - transient private boolean allowedQuickSearch = true; - transient private KeyAdapter quickSearchKeyAdapter; + transient private QuickSearch qs; /** wait cursor is shown automatically during expanding */ transient private boolean autoWaitCursor = true; @@ -454,7 +456,7 @@ * @return true if quick search feature is enabled, false otherwise. */ public boolean isQuickSearchAllowed() { - return allowedQuickSearch; + return qs.isEnabled(); } /** @@ -463,15 +465,7 @@ * @param allowedQuickSearch true if quick search shall be enabled */ public void setQuickSearchAllowed(boolean allowedQuickSearch) { - this.allowedQuickSearch = allowedQuickSearch; - if (quickSearchKeyAdapter != null && tree != null) { - if (allowedQuickSearch) { - tree.addKeyListener(quickSearchKeyAdapter); - } else { - removeSearchField(); - tree.removeKeyListener(quickSearchKeyAdapter); - } - } + qs.setEnabled(allowedQuickSearch); } @@ -1683,11 +1677,28 @@ } @Override + public void add(Component comp, Object constraints) { + if (constraints == searchConstraints) { + searchPanel = comp; + constraints = null; + } + super.add(comp, constraints); + } + + @Override + public void remove(Component comp) { + if (comp == searchPanel) { + searchPanel = null; + } + super.remove(comp); + } + + @Override public Insets getInsets() { Insets res = getInnerInsets(); res = new Insets(res.top, res.left, res.bottom, res.right); - if( null != searchpanel && searchpanel.isVisible() ) { - res.bottom += searchpanel.getPreferredSize().height; + if( null != searchPanel && searchPanel.isVisible() ) { + res.bottom += searchPanel.getPreferredSize().height; } return res; } @@ -1701,130 +1712,12 @@ } TreePath[] origSelectionPaths = null; - JPanel searchpanel = null; - // searchTextField manages focus because it handles VK_TAB key - private JTextField searchTextField = new JTextField() { - @Override - public boolean isManagingFocus() { - return true; - } - - @Override - public void processKeyEvent(KeyEvent ke) { - //override the default handling so that - //the parent will never receive the escape key and - //close a modal dialog - if (ke.getKeyCode() == KeyEvent.VK_ESCAPE) { - removeSearchField(); - ke.consume(); - - // bugfix #32909, reqest focus when search field is removed - SwingUtilities.invokeLater( - new Runnable() { - //additional bugfix - do focus change later or removing - //the component while it's focused will cause focus to - //get transferred to the next component in the - //parent focusTraversalPolicy *after* our request - //focus completes, so focus goes into a black hole - Tim - @Override - public void run() { - if( null != tree ) - tree.requestFocus(); - } - } - ); - } else { - super.processKeyEvent(ke); - } - } - }; - private int originalScrollMode; - - private static class SearchPanel extends JPanel { - public SearchPanel() { - if( ViewUtil.isAquaLaF ) - setBorder(BorderFactory.createEmptyBorder(9,6,8,2)); - else - setBorder(BorderFactory.createEmptyBorder(2,6,2,2)); - setOpaque( true ); - } - - @Override - protected void paintComponent(Graphics g) { - if( ViewUtil.isAquaLaF && g instanceof Graphics2D ) { - Graphics2D g2d = (Graphics2D) g; - g2d.setPaint( new GradientPaint(0, 0, UIManager.getColor("NbExplorerView.quicksearch.background.top"), - 0, getHeight(), UIManager.getColor("NbExplorerView.quicksearch.background.bottom")));//NOI18N - g2d.fillRect(0, 0, getWidth(), getHeight()); - g2d.setColor( UIManager.getColor("NbExplorerView.quicksearch.border") ); //NOI18N - g2d.drawLine(0, 0, getWidth(), 0); - } else { - super.paintComponent(g); - } - } - } - - - private void prepareSearchPanel() { - if( searchpanel == null ) { - searchpanel = new SearchPanel(); - - JLabel lbl = new JLabel(NbBundle.getMessage(TreeView.class, "LBL_QUICKSEARCH")); //NOI18N - searchpanel.setLayout(new BoxLayout(searchpanel, BoxLayout.X_AXIS)); - searchpanel.add(lbl); - searchpanel.add(searchTextField); - lbl.setLabelFor(searchTextField); - searchTextField.setColumns(10); - searchTextField.setMaximumSize(searchTextField.getPreferredSize()); - searchTextField.putClientProperty("JTextField.variant", "search"); //NOI18N - lbl.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5)); - } - } - - /** - * Adds the search field to the tree. - */ - private void displaySearchField() { - if( null != searchpanel || !isQuickSearchAllowed()) - return; - - TreeView previousSearchField = lastSearchField.get(); - if (previousSearchField != null && previousSearchField != this) { - previousSearchField.removeSearchField(); - } - - JViewport vp = getViewport(); - originalScrollMode = vp.getScrollMode(); - vp.setScrollMode(JViewport.SIMPLE_SCROLL_MODE); - searchTextField.setFont(tree.getFont()); - prepareSearchPanel(); - add(searchpanel); - invalidate(); - revalidate(); - repaint(); - searchTextField.requestFocus(); - - lastSearchField = new WeakReference(this); - } - - /** - * Removes the search field from the tree. - */ - private void removeSearchField() { - if( null == searchpanel ) - return; - - if (tree.getSelectionPaths() == null && origSelectionPaths != null) { - tree.setSelectionPaths(origSelectionPaths); - } - - remove(searchpanel); - searchpanel = null; - origSelectionPaths = null; - getViewport().setScrollMode(originalScrollMode); - invalidate(); - revalidate(); - repaint(); + private Component searchPanel = null; + private final Object searchConstraints = new Object(); + + /** Called from tests */ + Component getSearchPanel() { + return searchPanel; } private class ExplorerScrollPaneLayout extends ScrollPaneLayout { @@ -1832,20 +1725,25 @@ @Override public void layoutContainer( Container parent ) { super.layoutContainer(parent); - if( null != searchpanel && searchpanel.isVisible() ) { + if( null != searchPanel && searchPanel.isVisible() ) { Insets innerInsets = getInnerInsets(); - Dimension prefSize = searchpanel.getPreferredSize(); - searchpanel.setBounds(innerInsets.left, parent.getHeight()-innerInsets.bottom-prefSize.height, + Dimension prefSize = searchPanel.getPreferredSize(); + searchPanel.setBounds(innerInsets.left, parent.getHeight()-innerInsets.bottom-prefSize.height, parent.getWidth()-innerInsets.left-innerInsets.right, prefSize.height); } } } - private final class ExplorerTree extends JTree implements Autoscroll { + private final class ExplorerTree extends JTree implements Autoscroll, QuickSearchListener { AutoscrollSupport support; private String maxPrefix; int SEARCH_FIELD_SPACE = 3; private boolean firstPaint = true; + /** The last search searchResults */ + private List searchResults = new ArrayList(); + /** The last selected index from the search searchResults. */ + private int currentSelectionIndex; + private String lastSearchText; ExplorerTree(TreeModel model) { @@ -2008,6 +1906,14 @@ new GuardedActions(3, fe); } + @Override + protected void processKeyEvent(KeyEvent e) { + qs.processKeyEvent(e); + if (!e.isConsumed()) { + super.processKeyEvent(e); + } + } + private void repaintSelection() { int first = getSelectionModel().getMinSelectionRow(); int last = getSelectionModel().getMaxSelectionRow(); @@ -2039,58 +1945,66 @@ private void setupSearch() { // Remove the default key listeners - KeyListener[] keyListeners = getListeners(KeyListener.class); - - for (int i = 0; i < keyListeners.length; i++) { - removeKeyListener(keyListeners[i]); - } - - // create new key listeners - quickSearchKeyAdapter = ( - new KeyAdapter() { - @Override - public void keyTyped(KeyEvent e) { - int modifiers = e.getModifiers(); - int keyCode = e.getKeyCode(); - char c = e.getKeyChar(); - - //#43617 - don't eat + and - - //#98634 - and all its duplicates dont't react to space - if ((c == '+') || (c == '-') || (c==' ')) return; // NOI18N - - if (((modifiers > 0) && (modifiers != KeyEvent.SHIFT_MASK)) || e.isActionKey()) { - return; - } - - if (Character.isISOControl(c) || - (keyCode == KeyEvent.VK_SHIFT) || - (keyCode == KeyEvent.VK_ESCAPE)) return; - - final KeyStroke stroke = KeyStroke.getKeyStrokeForEvent(e); - origSelectionPaths = getSelectionPaths(); - if (origSelectionPaths != null && origSelectionPaths.length == 0) { - origSelectionPaths = null; - } - searchTextField.setText(String.valueOf(stroke.getKeyChar())); - - displaySearchField(); - e.consume(); - } - } - ); - if(isQuickSearchAllowed()){ - addKeyListener(quickSearchKeyAdapter); - } - // Create a the "multi-event" listener for the text field. Instead of - // adding separate instances of each needed listener, we're using a - // class which implements them all. This approach is used in order - // to avoid the creation of 4 instances which takes some time - SearchFieldListener searchFieldListener = new SearchFieldListener(); - searchTextField.addKeyListener(searchFieldListener); - searchTextField.addFocusListener(searchFieldListener); - searchTextField.getDocument().addDocumentListener(searchFieldListener); +// KeyListener[] keyListeners = getListeners(KeyListener.class); +// +// for (int i = 0; i < keyListeners.length; i++) { +// removeKeyListener(keyListeners[i]); +// } + + qs = QuickSearch.attach(TreeView.this, searchConstraints); + qs.addQuickSearchListener(this); } + @Override + public void quickSearchUpdate(String searchText) { + lastSearchText = searchText; + currentSelectionIndex = 0; + searchResults.clear(); + maxPrefix = null; + + String text = searchText.toUpperCase(); + + if (text.length() > 0) { + searchResults = doSearch(text); + } + displaySearchResult(); + } + + @Override + public void showNextSelection(Position.Bias bias) { + if (bias == Position.Bias.Forward) { + currentSelectionIndex++; + } else { + currentSelectionIndex--; + } + displaySearchResult(); + } + + @Override + public String findMaxPrefix(String prefix) { + return maxPrefix; + } + + @Override + public void quickSearchConfirmed() { + TreePath selectedTPath = getSelectionPath(); + if (selectedTPath != null) { + TreeNode selectedTNode = (TreeNode) selectedTPath.getLastPathComponent(); + Node selectedNode = Visualizer.findNode(selectedTNode); + performPreferredActionOnNodes(new Node[] { selectedNode }); + } + origSelectionPaths = null; + searchResults.clear(); + lastSearchText = null; + } + + @Override + public void quickSearchCanceled() { + origSelectionPaths = null; + searchResults.clear(); + lastSearchText = null; + } + private List doSearch(String prefix) { List results = new ArrayList(); Set resSet = new HashSet(); @@ -2134,7 +2048,7 @@ maxPrefix = elementName; } - maxPrefix = findMaxPrefix(maxPrefix, elementName); + maxPrefix = QuickSearch.findMaxCommonSubstring(maxPrefix, elementName, true); } // try next element startIndex++; @@ -2146,16 +2060,6 @@ return results; } - private String findMaxPrefix(String str1, String str2) { - String res = null; - - for (int i = 0; str1.regionMatches(true, 0, str2, 0, i); i++) { - res = str1.substring(0, i); - } - - return res; - } - /** * Copied and adapted from JTree.getNextMatch(...). * @@ -2193,6 +2097,29 @@ return null; } + private void displaySearchResult() { + int sz = searchResults.size(); + + if (sz > 0) { + if (currentSelectionIndex < 0) { + currentSelectionIndex = sz - 1; + } else if (currentSelectionIndex >= sz) { + currentSelectionIndex = 0; + } + + TreePath path = searchResults.get(currentSelectionIndex); + setSelectionPath(path); + scrollPathToVisible(path); + } else { + if (lastSearchText.isEmpty() && origSelectionPaths != null) { + setSelectionPaths(origSelectionPaths); + scrollPathToVisible(origSelectionPaths[0]); + } else { + clearSelection(); + } + } + } + /** notify the Component to autoscroll */ @Override public void autoscroll(Point cursorLoc) { @@ -2295,131 +2222,6 @@ } } - private class SearchFieldListener extends KeyAdapter implements DocumentListener, FocusListener { - /** The last search results */ - private List results = new ArrayList(); - - /** The last selected index from the search results. */ - private int currentSelectionIndex; - - SearchFieldListener() { - } - - @Override - public void changedUpdate(DocumentEvent e) { - searchForNode(); - } - - @Override - public void insertUpdate(DocumentEvent e) { - searchForNode(); - } - - @Override - public void removeUpdate(DocumentEvent e) { - searchForNode(); - } - - @Override - public void keyPressed(KeyEvent e) { - int keyCode = e.getKeyCode(); - - if (keyCode == KeyEvent.VK_ESCAPE) { - removeSearchField(); - ExplorerTree.this.requestFocus(); - } else if (keyCode == KeyEvent.VK_UP || (keyCode == KeyEvent.VK_F3 && e.isShiftDown())) { - currentSelectionIndex--; - displaySearchResult(); - - // Stop processing the event here. Otherwise it's dispatched - // to the tree too (which scrolls) - e.consume(); - } else if (keyCode == KeyEvent.VK_DOWN || keyCode == KeyEvent.VK_F3) { - currentSelectionIndex++; - displaySearchResult(); - - // Stop processing the event here. Otherwise it's dispatched - // to the tree too (which scrolls) - e.consume(); - } else if (keyCode == KeyEvent.VK_TAB) { - if (maxPrefix != null) { - searchTextField.setText(maxPrefix); - } - - e.consume(); - } else if (keyCode == KeyEvent.VK_ENTER) { - removeSearchField(); - - // bugfix #39607, don't expand selected node when default action invoked - TreePath selectedTPath = getSelectionPath(); - - if (selectedTPath != null) { - TreeNode selectedTNode = (TreeNode) selectedTPath.getLastPathComponent(); - Node selectedNode = Visualizer.findNode(selectedTNode); - - if ( - (selectedNode.getPreferredAction() == null) || - !selectedNode.getPreferredAction().isEnabled() - ) { - expandPath(getSelectionPath()); - } - } - - ExplorerTree.this.requestFocus(); - ExplorerTree.this.dispatchEvent(e); - } - } - - /** Searches for a node in the tree. */ - private void searchForNode() { - currentSelectionIndex = 0; - results.clear(); - maxPrefix = null; - - String text = searchTextField.getText().toUpperCase(); - - if (text.length() > 0) { - results = doSearch(text); - } - displaySearchResult(); - } - - private void displaySearchResult() { - int sz = results.size(); - - if (sz > 0) { - if (currentSelectionIndex < 0) { - currentSelectionIndex = sz - 1; - } else if (currentSelectionIndex >= sz) { - currentSelectionIndex = 0; - } - - TreePath path = results.get(currentSelectionIndex); - setSelectionPath(path); - scrollPathToVisible(path); - } else { - if (searchTextField.getText().length() == 0 && origSelectionPaths != null) { - setSelectionPaths(origSelectionPaths); - scrollPathToVisible(origSelectionPaths[0]); - } else { - clearSelection(); - } - } - } - - @Override - public void focusGained(FocusEvent e) { - // make sure nothing is selected - searchTextField.select(1, 1); - } - - @Override - public void focusLost(FocusEvent e) { - results.clear(); - removeSearchField(); - } - } - private class AccessibleExplorerTree extends JTree.AccessibleJTree { AccessibleExplorerTree() { } --- a/openide.explorer/test/unit/src/org/openide/explorer/view/TreeViewQuickSearchTest.java +++ a/openide.explorer/test/unit/src/org/openide/explorer/view/TreeViewQuickSearchTest.java @@ -219,9 +219,9 @@ if (phase[0] != 0) { if (btv.isQuickSearchAllowed()) { - assertNotNull("Quick Search enabled ", btv.searchpanel); + assertNotNull("Quick Search enabled ", btv.getSearchPanel()); } else { - assertNull("Quick Search disable", btv.searchpanel); + assertNull("Quick Search disable", btv.getSearchPanel()); } } }