This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

View | Details | Raw Unified | Return to bug 208794
Collapse All | Expand All

(-)a/openide.explorer/apichanges.xml (+14 lines)
Lines 50-55 Link Here
50
<apidef name="explorer">Explorer API</apidef>
50
<apidef name="explorer">Explorer API</apidef>
51
</apidefs>
51
</apidefs>
52
<changes>
52
<changes>
53
    <change id="OutlineView_with_quicksearch">
54
        <api name="explorer"/>
55
        <summary>QuickSearch attached to OutlineView</summary>
56
        <version major="6" minor="43"/>
57
        <date day="29" month="2" year="2012"/>
58
        <author login="mentlicher"/>
59
        <compatibility binary="compatible" source="compatible" deprecation="no" deletion="no" addition="yes"/>
60
        <description>
61
            Added <a href="@TOP@/org/openide/explorer/view/OutlineView.html#getQuickSearch()">OutlineView.getQuickSearch()</a>
62
            method to get and control quick search functionality on OutlineView.
63
        </description>
64
        <class package="org.openide.explorer.view" name="OutlineView"/>
65
        <issue number="110686"/>
66
    </change>
53
    <change id="PropertyEnv.create">
67
    <change id="PropertyEnv.create">
54
        <api name="explorer"/>
68
        <api name="explorer"/>
55
        <summary>API method for creating a PropertyEnv instance</summary>
69
        <summary>API method for creating a PropertyEnv instance</summary>
(-)a/openide.explorer/manifest.mf (-1 / +1 lines)
Lines 2-6 Link Here
2
OpenIDE-Module: org.openide.explorer
2
OpenIDE-Module: org.openide.explorer
3
OpenIDE-Module-Localizing-Bundle: org/openide/explorer/Bundle.properties
3
OpenIDE-Module-Localizing-Bundle: org/openide/explorer/Bundle.properties
4
AutoUpdate-Essential-Module: true
4
AutoUpdate-Essential-Module: true
5
OpenIDE-Module-Specification-Version: 6.42
5
OpenIDE-Module-Specification-Version: 6.43
6
6
(-)a/openide.explorer/nbproject/project.xml (-1 / +1 lines)
Lines 70-76 Link Here
70
                    <build-prerequisite/>
70
                    <build-prerequisite/>
71
                    <compile-dependency/>
71
                    <compile-dependency/>
72
                    <run-dependency>
72
                    <run-dependency>
73
                        <specification-version>6.2</specification-version>
73
                        <specification-version>7.43</specification-version>
74
                    </run-dependency>
74
                    </run-dependency>
75
                </dependency>
75
                </dependency>
76
                <dependency>
76
                <dependency>
(-)a/openide.explorer/src/org/openide/explorer/view/OutlineView.java (-15 / +6 lines)
Lines 132-137 Link Here
132
import org.netbeans.swing.outline.TreePathSupport;
132
import org.netbeans.swing.outline.TreePathSupport;
133
import org.openide.awt.Mnemonics;
133
import org.openide.awt.Mnemonics;
134
import org.openide.awt.MouseUtils;
134
import org.openide.awt.MouseUtils;
135
import org.openide.awt.QuickSearch;
135
import org.openide.explorer.ExplorerManager;
136
import org.openide.explorer.ExplorerManager;
136
import org.openide.explorer.ExplorerUtils;
137
import org.openide.explorer.ExplorerUtils;
137
import org.openide.explorer.propertysheet.PropertyPanel;
138
import org.openide.explorer.propertysheet.PropertyPanel;
Lines 681-704 Link Here
681
    }
682
    }
682
    
683
    
683
    /**
684
    /**
684
     * Test whether the quick search feature is enabled or not.
685
     * Get the quick search support, that is attached to this view.
685
     * Default is enabled (true).
686
     * @return The quick search support.
686
     * @since 
687
     * @since 6.43
687
     * @return <code>true</code> if quick search feature is enabled, <code>false</code> otherwise.
688
     */
688
     */
689
    /*public*/ boolean isQuickSearchAllowed() {
689
    public final QuickSearch getQuickSearch() {
690
        return quickSearch.isEnabled();
690
        return quickSearch;
691
    }
691
    }
692
    
692
    
693
    /**
694
     * Set whether the quick search feature is enabled or not.
695
     * @since 
696
     * @param allowedQuickSearch <code>true</code> if quick search shall be enabled
697
     */
698
    /*public*/ void setQuickSearchAllowed(boolean allowedQuickSearch) {
699
        quickSearch.setEnabled(allowedQuickSearch);
700
    }
701
702
    /** Initializes the component and lookup explorer manager.
693
    /** Initializes the component and lookup explorer manager.
703
     */
694
     */
704
    @Override
695
    @Override
(-)a/openide.explorer/src/org/openide/explorer/view/QuickSearch.java (-514 lines)
Lines 1-514 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2012 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2012 Sun Microsystems, Inc.
41
 */
42
package org.openide.explorer.view;
43
44
import java.awt.*;
45
import java.awt.event.*;
46
import java.lang.ref.WeakReference;
47
import java.util.LinkedList;
48
import java.util.List;
49
import javax.swing.*;
50
import javax.swing.event.DocumentEvent;
51
import javax.swing.event.DocumentListener;
52
import javax.swing.text.Position.Bias;
53
54
/**
55
 * Quick search infrastructure
56
 * 
57
 * @author Martin Entlicher
58
 */
59
class QuickSearch {
60
    
61
    private static final String ICON_FIND = "org/openide/explorer/view/find.png";
62
    private static final String ICON_FIND_WITH_MENU = "org/openide/explorer/view/findMenu.png";
63
    
64
    private final JComponent component;
65
    private final Object constraints;
66
    private boolean enabled = true;
67
    private final List<QuickSearchListener> listeners = new LinkedList<QuickSearchListener>();
68
    private SearchTextField searchTextField;
69
    private KeyAdapter quickSearchKeyAdapter;
70
    private JPanel searchPanel;
71
    private JMenu popupMenu;
72
    
73
    private QuickSearch(JComponent component, Object constraints) {
74
        this.component = component;
75
        this.constraints = constraints;
76
        setUpSearch();
77
    }
78
    
79
    public static QuickSearch attach(JComponent component, Object constraints) {
80
        Object qso = component.getClientProperty(QuickSearch.class.getName());
81
        if (qso instanceof QuickSearch) {
82
            return (QuickSearch) qso;
83
        } else {
84
            QuickSearch qs = new QuickSearch(component, constraints);
85
            component.putClientProperty(QuickSearch.class.getName(), qs);
86
            return qs;
87
        }
88
    }
89
    
90
    public void detach() {
91
        setEnabled(false);
92
        component.putClientProperty(QuickSearch.class.getName(), null);
93
    }
94
    
95
    public boolean isEnabled() {
96
        return enabled;
97
    }
98
    
99
    public void setEnabled(boolean enabled) {
100
        if (this.enabled == enabled) {
101
            return ;
102
        }
103
        this.enabled = enabled;
104
        if (enabled) {
105
            component.addKeyListener(quickSearchKeyAdapter);
106
        } else {
107
            component.removeKeyListener(quickSearchKeyAdapter);
108
        }
109
    }
110
    
111
    public void addQuickSearchListener(QuickSearchListener qsl) {
112
        synchronized (listeners) {
113
            listeners.add(qsl);
114
        }
115
    }
116
    
117
    public void removeQuickSearchListener(QuickSearchListener qsl) {
118
        synchronized (listeners) {
119
            listeners.remove(qsl);
120
        }
121
    }
122
    
123
    public void setPopupMenu(JMenu popupMenu) {
124
        this.popupMenu = popupMenu;
125
    }
126
    
127
    public void processKeyEvent(KeyEvent ke) {
128
        switch(ke.getID()) {
129
            case KeyEvent.KEY_PRESSED:
130
                quickSearchKeyAdapter.keyPressed(ke);
131
                break;
132
            case KeyEvent.KEY_RELEASED:
133
                quickSearchKeyAdapter.keyReleased(ke);
134
                break;
135
            case KeyEvent.KEY_TYPED:
136
                quickSearchKeyAdapter.keyTyped(ke);
137
                break;
138
        }
139
    }
140
    
141
    private QuickSearchListener[] getQuickSearchListeners() {
142
        QuickSearchListener[] qsls;
143
        synchronized (listeners) {
144
            qsls = listeners.toArray(new QuickSearchListener[] {});
145
        }
146
        return qsls;
147
    }
148
    
149
    private void fireQuickSearchUpdate(String searchText) {
150
        for (QuickSearchListener qsl : getQuickSearchListeners()) {
151
            qsl.quickSearchUpdate(searchText);
152
        }
153
    }
154
    
155
    private void fireShowNextSelection(Bias bias) {
156
        for (QuickSearchListener qsl : getQuickSearchListeners()) {
157
            qsl.showNextSelection(bias);
158
        }
159
    }
160
    
161
    private String findMaxPrefix(String prefix) {
162
        for (QuickSearchListener qsl : getQuickSearchListeners()) {
163
            prefix = qsl.findMaxPrefix(prefix);
164
        }
165
        return prefix;
166
    }
167
    
168
    private void fireQuickSearchConfirmed() {
169
        for (QuickSearchListener qsl : getQuickSearchListeners()) {
170
            qsl.quickSearchConfirmed();
171
        }
172
    }
173
    
174
    private void fireQuickSearchCanceled() {
175
        for (QuickSearchListener qsl : getQuickSearchListeners()) {
176
            qsl.quickSearchCanceled();
177
        }
178
    }
179
    
180
    private void setUpSearch() {
181
        searchTextField = new SearchTextField();
182
        // create new key listeners
183
        quickSearchKeyAdapter = (
184
            new KeyAdapter() {
185
            @Override
186
                public void keyTyped(KeyEvent e) {
187
                    int modifiers = e.getModifiers();
188
                    int keyCode = e.getKeyCode();
189
                    char c = e.getKeyChar();
190
191
                    //#43617 - don't eat + and -
192
                    //#98634 - and all its duplicates dont't react to space
193
                    if ((c == '+') || (c == '-') || (c==' ')) return; // NOI18N
194
195
                    if (((modifiers > 0) && (modifiers != KeyEvent.SHIFT_MASK)) || e.isActionKey()) {
196
                        return;
197
                    }
198
199
                    if (Character.isISOControl(c) ||
200
                            (keyCode == KeyEvent.VK_SHIFT) ||
201
                            (keyCode == KeyEvent.VK_ESCAPE)) return;
202
203
                    final KeyStroke stroke = KeyStroke.getKeyStrokeForEvent(e);
204
                    searchTextField.setText(String.valueOf(stroke.getKeyChar()));
205
206
                    displaySearchField();
207
                    e.consume();
208
                }
209
            }
210
        );
211
        if(isEnabled()){
212
            component.addKeyListener(quickSearchKeyAdapter);
213
        }
214
        // Create a the "multi-event" listener for the text field. Instead of
215
        // adding separate instances of each needed listener, we're using a
216
        // class which implements them all. This approach is used in order 
217
        // to avoid the creation of 4 instances which takes some time
218
        SearchFieldListener searchFieldListener = new SearchFieldListener();
219
        searchTextField.addKeyListener(searchFieldListener);
220
        searchTextField.addFocusListener(searchFieldListener);
221
        searchTextField.getDocument().addDocumentListener(searchFieldListener);
222
        
223
    }
224
    
225
    private void displaySearchField() {
226
        if (searchPanel != null || !isEnabled()) {
227
            return;
228
        }
229
        /*
230
        TreeView previousSearchField = lastSearchField.get();
231
        if (previousSearchField != null && previousSearchField != this) {
232
            previousSearchField.removeSearchField();
233
        }
234
        */
235
        //JViewport vp = getViewport();
236
        //originalScrollMode = vp.getScrollMode();
237
        //vp.setScrollMode(JViewport.SIMPLE_SCROLL_MODE);
238
        searchTextField.setOriginalFocusOwner();
239
        searchTextField.setFont(component.getFont());
240
        searchPanel = new SearchPanel();
241
        //JLabel lbl = new JLabel(NbBundle.getMessage(TreeView.class, "LBL_QUICKSEARCH")); //NOI18N
242
        final JLabel lbl;
243
        if (popupMenu != null) {
244
            lbl = new JLabel(org.openide.util.ImageUtilities.loadImageIcon(ICON_FIND_WITH_MENU, false));
245
            lbl.addMouseListener(new MouseAdapter() {
246
                @Override
247
                public void mousePressed(MouseEvent e) {
248
                    if (e != null && !SwingUtilities.isLeftMouseButton(e)) {
249
                        return;
250
                    }
251
                    JPopupMenu pm = popupMenu.getPopupMenu();
252
                    pm.show(lbl, 0, lbl.getHeight() - 1);
253
                }
254
            });
255
        } else {
256
            lbl = new JLabel(org.openide.util.ImageUtilities.loadImageIcon(ICON_FIND, false));
257
        }
258
        searchPanel.setLayout(new BoxLayout(searchPanel, BoxLayout.X_AXIS));
259
        searchPanel.add(lbl);
260
        searchPanel.add(searchTextField);
261
        lbl.setLabelFor(searchTextField);
262
        searchTextField.setColumns(10);
263
        searchTextField.setMaximumSize(searchTextField.getPreferredSize());
264
        searchTextField.putClientProperty("JTextField.variant", "search"); //NOI18N
265
        lbl.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5));
266
        //JToggleButton matchCaseButton = new JToggleButton("aA");
267
        //matchCaseButton.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
268
        //searchPanel.add(matchCaseButton);
269
        if (component instanceof JScrollPane) {
270
        //    ((JScrollPane) component).getViewport().setScrollMode(JViewport.SIMPLE_SCROLL_MODE);
271
        }
272
        if (constraints == null) {
273
            component.add(searchPanel);
274
        } else {
275
            component.add(searchPanel, constraints);
276
        }
277
        component.invalidate();
278
        component.revalidate();
279
        component.repaint();
280
        searchTextField.requestFocus();
281
    }
282
    
283
    private void removeSearchField() {
284
        if (searchPanel == null) {
285
            return;
286
        }
287
        component.remove(searchPanel);
288
        searchPanel = null;
289
        //getViewport().setScrollMode(originalScrollMode);
290
        component.invalidate();
291
        component.revalidate();
292
        component.repaint();
293
    }
294
    
295
    public static String findMaxCommonSubstring(String str1, String str2, boolean ignoreCase) {
296
        int n1 = str1.length();
297
        int n2 = str2.length();
298
        int i = 0;
299
        if (ignoreCase) {
300
            for ( ; i < n1 && i < n2; i++) {
301
                char c1 = Character.toUpperCase(str1.charAt(i));
302
                char c2 = Character.toUpperCase(str2.charAt(i));
303
                if (c1 != c2) {
304
                    break;
305
                }
306
            }
307
        } else {
308
            for ( ; i < n1 && i < n2; i++) {
309
                char c1 = str1.charAt(i);
310
                char c2 = str2.charAt(i);
311
                if (c1 != c2) {
312
                    break;
313
                }
314
            }
315
        }
316
        return str1.substring(0, i);
317
    }
318
319
    public static interface QuickSearchListener {
320
        
321
        void quickSearchUpdate(String searchText);
322
        
323
        void showNextSelection(Bias bias);
324
        
325
        String findMaxPrefix(String prefix);
326
        
327
        void quickSearchConfirmed();
328
        
329
        void quickSearchCanceled();
330
331
    }
332
    
333
    private static class SearchPanel extends JPanel {
334
        
335
        public SearchPanel() {
336
            if (ViewUtil.isAquaLaF) {
337
                setBorder(BorderFactory.createEmptyBorder(9,6,8,2));
338
            } else {
339
                setBorder(BorderFactory.createEmptyBorder(2,6,2,2));
340
            }
341
            setOpaque(true);
342
        }
343
344
        @Override
345
        protected void paintComponent(Graphics g) {
346
            if (ViewUtil.isAquaLaF && g instanceof Graphics2D) {
347
                Graphics2D g2d = (Graphics2D) g;
348
                g2d.setPaint(new GradientPaint(0, 0, UIManager.getColor("NbExplorerView.quicksearch.background.top"),
349
                        0, getHeight(), UIManager.getColor("NbExplorerView.quicksearch.background.bottom")));//NOI18N
350
                g2d.fillRect(0, 0, getWidth(), getHeight());
351
                g2d.setColor(UIManager.getColor("NbExplorerView.quicksearch.border")); //NOI18N
352
                g2d.drawLine(0, 0, getWidth(), 0);
353
            } else {
354
                super.paintComponent(g);
355
            }
356
        }
357
    }
358
359
    /** searchTextField manages focus because it handles VK_ESCAPE key */
360
    private class SearchTextField extends JTextField {
361
        
362
        private WeakReference<Component> originalFocusOwner = new WeakReference<Component>(null);
363
        
364
        public SearchTextField() {
365
        }
366
        
367
        void setOriginalFocusOwner() {
368
            Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
369
            if (focusOwner != null && component.isAncestorOf(focusOwner)) {
370
                originalFocusOwner = new WeakReference<Component>(focusOwner);
371
            } else {
372
                originalFocusOwner = new WeakReference<Component>(component);
373
            }
374
        }
375
        
376
        void requestOriginalFocusOwner() {
377
            SwingUtilities.invokeLater(
378
                new Runnable() {
379
                    //additional bugfix - do focus change later or removing
380
                    //the component while it's focused will cause focus to
381
                    //get transferred to the next component in the
382
                    //parent focusTraversalPolicy *after* our request
383
                    //focus completes, so focus goes into a black hole - Tim
384
                    @Override
385
                    public void run() {
386
                        Component fo = originalFocusOwner.get();
387
                        if (fo != null) {
388
                            fo.requestFocusInWindow();
389
                        }
390
                    }
391
                }
392
            );
393
        }
394
        
395
        @Override
396
        public boolean isManagingFocus() {
397
            return true;
398
        }
399
400
        @Override
401
        public void processKeyEvent(KeyEvent ke) {
402
            //override the default handling so that
403
            //the parent will never receive the escape key and
404
            //close a modal dialog
405
            if (ke.getKeyCode() == KeyEvent.VK_ESCAPE) {
406
                removeSearchField();
407
                ke.consume();
408
                // bugfix #32909, reqest focus when search field is removed
409
                requestOriginalFocusOwner();
410
                fireQuickSearchCanceled();
411
            } else {
412
                super.processKeyEvent(ke);
413
            }
414
        }
415
    };
416
    
417
    private class SearchFieldListener extends KeyAdapter implements DocumentListener, FocusListener {
418
        
419
        private boolean ignoreEvents;
420
421
        SearchFieldListener() {
422
        }
423
424
        @Override
425
        public void changedUpdate(DocumentEvent e) {
426
            if (ignoreEvents) return;
427
            searchForNode();
428
        }
429
430
        @Override
431
        public void insertUpdate(DocumentEvent e) {
432
            if (ignoreEvents) return;
433
            searchForNode();
434
        }
435
436
        @Override
437
        public void removeUpdate(DocumentEvent e) {
438
            if (ignoreEvents) return;
439
            searchForNode();
440
        }
441
442
        @Override
443
        public void keyPressed(KeyEvent e) {
444
            int keyCode = e.getKeyCode();
445
446
            if (keyCode == KeyEvent.VK_ESCAPE) {
447
                removeSearchField();
448
                searchTextField.requestOriginalFocusOwner();
449
                fireQuickSearchCanceled();
450
                e.consume();
451
            } else if (keyCode == KeyEvent.VK_UP || (keyCode == KeyEvent.VK_F3 && e.isShiftDown())) {
452
                fireShowNextSelection(Bias.Backward);
453
                // Stop processing the event here. Otherwise it's dispatched
454
                // to the tree too (which scrolls)
455
                e.consume();
456
            } else if (keyCode == KeyEvent.VK_DOWN || keyCode == KeyEvent.VK_F3) {
457
                fireShowNextSelection(Bias.Forward);
458
                // Stop processing the event here. Otherwise it's dispatched
459
                // to the tree too (which scrolls)
460
                e.consume();
461
            } else if (keyCode == KeyEvent.VK_TAB) {
462
                String maxPrefix = findMaxPrefix(searchTextField.getText());
463
                ignoreEvents = true;
464
                try {
465
                    searchTextField.setText(maxPrefix);
466
                } finally {
467
                    ignoreEvents = false;
468
                }
469
470
                e.consume();
471
            } else if (keyCode == KeyEvent.VK_ENTER) {
472
                removeSearchField();
473
                fireQuickSearchConfirmed();
474
475
                component.requestFocusInWindow();
476
                e.consume();
477
            }
478
        }
479
480
        /** Searches for a node in the tree. */
481
        private void searchForNode() {
482
            String text = searchTextField.getText();
483
            fireQuickSearchUpdate(text);
484
        }
485
486
        @Override
487
        public void focusGained(FocusEvent e) {
488
            if (e.getSource() == searchTextField) {
489
                // make sure nothing is selected
490
                int n = searchTextField.getText().length();
491
                searchTextField.select(n, n);
492
            }
493
        }
494
495
        @Override
496
        public void focusLost(FocusEvent e) {
497
            if (e.isTemporary()) return ;
498
            Component oppositeComponent = e.getOppositeComponent();
499
            if (e.getSource() != searchTextField) {
500
                ((Component) e.getSource()).removeFocusListener(this);
501
            }
502
            if (oppositeComponent instanceof JMenuItem || oppositeComponent instanceof JPopupMenu) {
503
                oppositeComponent.addFocusListener(this);
504
                return ;
505
            }
506
            if (oppositeComponent == searchTextField) {
507
                return ;
508
            }
509
            removeSearchField();
510
            fireQuickSearchConfirmed();
511
        }
512
    }
513
514
}
(-)a/openide.explorer/src/org/openide/explorer/view/TableQuickSearchSupport.java (-14 / +15 lines)
Lines 61-67 Link Here
61
import javax.swing.event.ChangeListener;
61
import javax.swing.event.ChangeListener;
62
import javax.swing.table.TableColumn;
62
import javax.swing.table.TableColumn;
63
import javax.swing.table.TableColumnModel;
63
import javax.swing.table.TableColumnModel;
64
import javax.swing.text.Position;
64
import org.openide.awt.QuickSearch;
65
import org.openide.awt.QuickSearchListener;
65
import org.openide.util.NbBundle;
66
import org.openide.util.NbBundle;
66
import org.openide.util.NbPreferences;
67
import org.openide.util.NbPreferences;
67
68
Lines 70-76 Link Here
70
 * 
71
 * 
71
 * @author Martin Entlicher
72
 * @author Martin Entlicher
72
 */
73
 */
73
class TableQuickSearchSupport implements QuickSearch.QuickSearchListener {
74
class TableQuickSearchSupport implements QuickSearchListener {
74
    
75
    
75
    private int quickSearchInitialRow = -1;     // The search was initiated here
76
    private int quickSearchInitialRow = -1;     // The search was initiated here
76
    private int quickSearchInitialColumn = -1;  // The search was initiated here
77
    private int quickSearchInitialColumn = -1;  // The search was initiated here
Lines 103-114 Link Here
103
        }
104
        }
104
        quickSearchLastRow = quickSearchInitialRow;
105
        quickSearchLastRow = quickSearchInitialRow;
105
        quickSearchLastColumn = quickSearchInitialColumn;
106
        quickSearchLastColumn = quickSearchInitialColumn;
106
        doSearch(searchText, Position.Bias.Forward);
107
        doSearch(searchText, true);
107
    }
108
    }
108
109
109
    @Override
110
    @Override
110
    public void showNextSelection(Position.Bias bias) {
111
    public void showNextSelection(boolean forward) {
111
        if (bias == Position.Bias.Forward) {
112
        if (forward) {
112
            if (++quickSearchLastColumn >= table.getColumnCount()) {
113
            if (++quickSearchLastColumn >= table.getColumnCount()) {
113
                quickSearchLastColumn = 0;
114
                quickSearchLastColumn = 0;
114
                if (++quickSearchLastRow >= table.getRowCount()) {
115
                if (++quickSearchLastRow >= table.getRowCount()) {
Lines 116-122 Link Here
116
                }
117
                }
117
            }
118
            }
118
        }
119
        }
119
        doSearch(lastSearchText, bias);
120
        doSearch(lastSearchText, forward);
120
    }
121
    }
121
122
122
    @Override
123
    @Override
Lines 145-151 Link Here
145
                    if (maxPrefix == null) {
146
                    if (maxPrefix == null) {
146
                        maxPrefix = str;
147
                        maxPrefix = str;
147
                    } else {
148
                    } else {
148
                        maxPrefix = QuickSearch.findMaxCommonSubstring(maxPrefix, str, !qss.isMatchCase());
149
                        maxPrefix = QuickSearch.findMaxPrefix(maxPrefix, str, !qss.isMatchCase());
149
                    }
150
                    }
150
                }
151
                }
151
            }
152
            }
Lines 170-190 Link Here
170
        quickSearchInitialColumn = -1;
171
        quickSearchInitialColumn = -1;
171
    }
172
    }
172
173
173
    private void doSearch(String searchText, Position.Bias bias) {
174
    private void doSearch(String searchText, boolean forward) {
174
        if (!qss.isMatchCase()) {
175
        if (!qss.isMatchCase()) {
175
            searchText = searchText.toUpperCase();
176
            searchText = searchText.toUpperCase();
176
        }
177
        }
177
        int n = table.getRowCount();
178
        int n = table.getRowCount();
178
        boolean backward = bias == Position.Bias.Backward;
179
        //boolean backward = bias == Position.Bias.Backward;
179
        int row1 = quickSearchLastRow;
180
        int row1 = quickSearchLastRow;
180
        int row2 = quickSearchLastRow + n;
181
        int row2 = quickSearchLastRow + n;
181
        boolean lineStartSearch = true;
182
        boolean lineStartSearch = true;
182
        Set<String> columnsIgnoredToSearch = qss.getColumnsIgnoredToSearch();
183
        Set<String> columnsIgnoredToSearch = qss.getColumnsIgnoredToSearch();
183
        do {
184
        do {
184
            int col1 = quickSearchLastColumn;
185
            int col1 = quickSearchLastColumn;
185
            int col2 = (backward) ? 0 : table.getColumnCount();
186
            int col2 = (forward) ? table.getColumnCount() : 0;
186
            for (int row = (backward) ? (row2 - 1) : row1; (backward) ? row >= row1 : row < row2; row = (backward) ? --row : ++row) {
187
            for (int row = (forward) ? row1 : (row2 - 1); (forward) ? row < row2 : row >= row1; row = (forward) ? ++row : --row) {
187
                for (int col = col1; (backward) ? col >= col2 : col < col2; col = (backward) ? --col : ++col) {
188
                for (int col = col1; (forward) ? col < col2 : col >= col2; col = (forward) ? ++col : --col) {
188
                    String cName = table.getColumnName(col);
189
                    String cName = table.getColumnName(col);
189
                    if (columnsIgnoredToSearch.contains(cName)) {
190
                    if (columnsIgnoredToSearch.contains(cName)) {
190
                        continue;
191
                        continue;
Lines 208-214 Link Here
208
                        }
209
                        }
209
                    }
210
                    }
210
                }
211
                }
211
                col1 = (backward) ? table.getColumnCount() - 1 : 0;
212
                col1 = (forward) ? 0 : table.getColumnCount() - 1;
212
            }
213
            }
213
            lineStartSearch = !lineStartSearch;
214
            lineStartSearch = !lineStartSearch;
214
        } while (!lineStartSearch);
215
        } while (!lineStartSearch);
Lines 234-240 Link Here
234
                return TableQuickSearchSupport.getSearchPopupMenu(qss, table.getColumnModel(), new ActionListener() {
235
                return TableQuickSearchSupport.getSearchPopupMenu(qss, table.getColumnModel(), new ActionListener() {
235
                    @Override
236
                    @Override
236
                    public void actionPerformed(ActionEvent e) {
237
                    public void actionPerformed(ActionEvent e) {
237
                        doSearch(lastSearchText, Position.Bias.Forward);
238
                        doSearch(lastSearchText, true);
238
                    }
239
                    }
239
                });
240
                });
240
            }
241
            }
(-)a/openide.explorer/src/org/openide/explorer/view/TreeTableView.java (+1 lines)
Lines 77-82 Link Here
77
import javax.swing.plaf.metal.MetalScrollBarUI;
77
import javax.swing.plaf.metal.MetalScrollBarUI;
78
import javax.swing.table.*;
78
import javax.swing.table.*;
79
import javax.swing.tree.*;
79
import javax.swing.tree.*;
80
import org.openide.awt.QuickSearch;
80
import org.openide.explorer.view.TreeView.PopupAdapter;
81
import org.openide.explorer.view.TreeView.PopupAdapter;
81
import org.openide.explorer.view.TreeView.PopupSupport;
82
import org.openide.explorer.view.TreeView.PopupSupport;
82
import org.openide.explorer.view.TreeView.TreePropertyListener;
83
import org.openide.explorer.view.TreeView.TreePropertyListener;
(-)a/openide.explorer/src/org/openide/explorer/view/TreeView.java (-332 / +132 lines)
Lines 133-139 Link Here
133
import javax.swing.event.TreeWillExpandListener;
133
import javax.swing.event.TreeWillExpandListener;
134
import javax.swing.plaf.TreeUI;
134
import javax.swing.plaf.TreeUI;
135
import javax.swing.plaf.UIResource;
135
import javax.swing.plaf.UIResource;
136
import javax.swing.text.Position;
137
import javax.swing.tree.ExpandVetoException;
136
import javax.swing.tree.ExpandVetoException;
138
import javax.swing.tree.RowMapper;
137
import javax.swing.tree.RowMapper;
139
import javax.swing.tree.TreeCellEditor;
138
import javax.swing.tree.TreeCellEditor;
Lines 141-146 Link Here
141
import javax.swing.tree.TreeNode;
140
import javax.swing.tree.TreeNode;
142
import javax.swing.tree.TreePath;
141
import javax.swing.tree.TreePath;
143
import javax.swing.tree.TreeSelectionModel;
142
import javax.swing.tree.TreeSelectionModel;
143
import org.openide.awt.QuickSearch;
144
import org.openide.awt.QuickSearchListener;
144
145
145
146
146
/**
147
/**
Lines 224-231 Link Here
224
    transient private int allowedDropActions = DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_REFERENCE;
225
    transient private int allowedDropActions = DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_REFERENCE;
225
    
226
    
226
    /** Quick Search support */
227
    /** Quick Search support */
227
    transient private boolean allowedQuickSearch = true;
228
    transient private QuickSearch qs;
228
    transient private KeyAdapter quickSearchKeyAdapter;
229
    
229
    
230
    /** wait cursor is shown automatically during expanding */
230
    /** wait cursor is shown automatically during expanding */
231
    transient private boolean autoWaitCursor = true;
231
    transient private boolean autoWaitCursor = true;
Lines 454-460 Link Here
454
     * @return true if quick search feature is enabled, false otherwise.
454
     * @return true if quick search feature is enabled, false otherwise.
455
     */
455
     */
456
    public boolean isQuickSearchAllowed() {
456
    public boolean isQuickSearchAllowed() {
457
        return allowedQuickSearch;
457
        return qs.isEnabled();
458
    }
458
    }
459
    
459
    
460
    /**
460
    /**
Lines 463-477 Link Here
463
     * @param allowedQuickSearch <code>true</code> if quick search shall be enabled
463
     * @param allowedQuickSearch <code>true</code> if quick search shall be enabled
464
     */
464
     */
465
    public void setQuickSearchAllowed(boolean allowedQuickSearch) {
465
    public void setQuickSearchAllowed(boolean allowedQuickSearch) {
466
        this.allowedQuickSearch = allowedQuickSearch;
466
        qs.setEnabled(allowedQuickSearch);
467
         if (quickSearchKeyAdapter != null && tree != null) {
468
            if (allowedQuickSearch) {
469
               tree.addKeyListener(quickSearchKeyAdapter);
470
            } else {
471
               removeSearchField();
472
               tree.removeKeyListener(quickSearchKeyAdapter);
473
            }
474
         }
475
    }
467
    }
476
468
477
    
469
    
Lines 1683-1693 Link Here
1683
    }
1675
    }
1684
1676
1685
    @Override
1677
    @Override
1678
    public void add(Component comp, Object constraints) {
1679
        if (constraints == searchConstraints) {
1680
            searchPanel = comp;
1681
            constraints = null;
1682
        }
1683
        super.add(comp, constraints);
1684
    }
1685
    
1686
    @Override
1687
    public void remove(Component comp) {
1688
        if (comp == searchPanel) {
1689
            searchPanel = null;
1690
        }
1691
        super.remove(comp);
1692
    }
1693
    
1694
    @Override
1686
    public Insets getInsets() {
1695
    public Insets getInsets() {
1687
        Insets res = getInnerInsets();
1696
        Insets res = getInnerInsets();
1688
        res = new Insets(res.top, res.left, res.bottom, res.right);
1697
        res = new Insets(res.top, res.left, res.bottom, res.right);
1689
        if( null != searchpanel && searchpanel.isVisible() ) {
1698
        if( null != searchPanel && searchPanel.isVisible() ) {
1690
            res.bottom += searchpanel.getPreferredSize().height;
1699
            res.bottom += searchPanel.getPreferredSize().height;
1691
        }
1700
        }
1692
        return res;
1701
        return res;
1693
    }
1702
    }
Lines 1701-1830 Link Here
1701
    }
1710
    }
1702
1711
1703
    TreePath[] origSelectionPaths = null;
1712
    TreePath[] origSelectionPaths = null;
1704
    JPanel searchpanel = null;
1713
    private Component searchPanel = null;
1705
    // searchTextField manages focus because it handles VK_TAB key
1714
    private final Object searchConstraints = new Object();
1706
    private JTextField searchTextField = new JTextField() {
1715
    
1707
        @Override
1716
    /** Called from tests */
1708
        public boolean isManagingFocus() {
1717
    Component getSearchPanel() {
1709
            return true;
1718
        return searchPanel;
1710
        }
1711
1712
        @Override
1713
        public void processKeyEvent(KeyEvent ke) {
1714
            //override the default handling so that
1715
            //the parent will never receive the escape key and
1716
            //close a modal dialog
1717
            if (ke.getKeyCode() == KeyEvent.VK_ESCAPE) {
1718
                removeSearchField();
1719
                ke.consume();
1720
1721
                // bugfix #32909, reqest focus when search field is removed
1722
                SwingUtilities.invokeLater(
1723
                    new Runnable() {
1724
                        //additional bugfix - do focus change later or removing
1725
                        //the component while it's focused will cause focus to
1726
                        //get transferred to the next component in the
1727
                        //parent focusTraversalPolicy *after* our request
1728
                        //focus completes, so focus goes into a black hole - Tim
1729
                        @Override
1730
                        public void run() {
1731
                            if( null != tree )
1732
                                tree.requestFocus();
1733
                        }
1734
                    }
1735
                );
1736
            } else {
1737
                super.processKeyEvent(ke);
1738
            }
1739
        }
1740
    };
1741
    private int originalScrollMode;
1742
1743
    private static class SearchPanel extends JPanel {
1744
        public SearchPanel() {
1745
            if( ViewUtil.isAquaLaF )
1746
                setBorder(BorderFactory.createEmptyBorder(9,6,8,2));
1747
            else
1748
                setBorder(BorderFactory.createEmptyBorder(2,6,2,2));
1749
            setOpaque( true );
1750
        }
1751
1752
        @Override
1753
        protected void paintComponent(Graphics g) {
1754
            if( ViewUtil.isAquaLaF && g instanceof Graphics2D ) {
1755
                Graphics2D g2d = (Graphics2D) g;
1756
                g2d.setPaint( new GradientPaint(0, 0, UIManager.getColor("NbExplorerView.quicksearch.background.top"),
1757
                        0, getHeight(), UIManager.getColor("NbExplorerView.quicksearch.background.bottom")));//NOI18N
1758
                g2d.fillRect(0, 0, getWidth(), getHeight());
1759
                g2d.setColor( UIManager.getColor("NbExplorerView.quicksearch.border") ); //NOI18N
1760
                g2d.drawLine(0, 0, getWidth(), 0);
1761
            } else {
1762
                super.paintComponent(g);
1763
            }
1764
        }
1765
    }
1766
1767
1768
    private void prepareSearchPanel() {
1769
        if( searchpanel == null ) {
1770
            searchpanel = new SearchPanel();
1771
1772
            JLabel lbl = new JLabel(NbBundle.getMessage(TreeView.class, "LBL_QUICKSEARCH")); //NOI18N
1773
            searchpanel.setLayout(new BoxLayout(searchpanel, BoxLayout.X_AXIS));
1774
            searchpanel.add(lbl);
1775
            searchpanel.add(searchTextField);
1776
            lbl.setLabelFor(searchTextField);
1777
            searchTextField.setColumns(10);
1778
            searchTextField.setMaximumSize(searchTextField.getPreferredSize());
1779
            searchTextField.putClientProperty("JTextField.variant", "search"); //NOI18N
1780
            lbl.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5));
1781
        }
1782
    }
1783
1784
    /**
1785
     * Adds the search field to the tree.
1786
     */
1787
    private void displaySearchField() {
1788
        if( null != searchpanel || !isQuickSearchAllowed())
1789
            return;
1790
1791
        TreeView previousSearchField = lastSearchField.get();
1792
        if (previousSearchField != null && previousSearchField != this) {
1793
            previousSearchField.removeSearchField();
1794
        }
1795
1796
        JViewport vp = getViewport();
1797
        originalScrollMode = vp.getScrollMode();
1798
        vp.setScrollMode(JViewport.SIMPLE_SCROLL_MODE);
1799
        searchTextField.setFont(tree.getFont());
1800
        prepareSearchPanel();
1801
        add(searchpanel);
1802
        invalidate();
1803
        revalidate();
1804
        repaint();
1805
        searchTextField.requestFocus();
1806
1807
        lastSearchField = new WeakReference<TreeView>(this);
1808
    }
1809
1810
    /**
1811
     * Removes the search field from the tree.
1812
     */
1813
    private void removeSearchField() {
1814
        if( null == searchpanel )
1815
            return;
1816
1817
        if (tree.getSelectionPaths() == null && origSelectionPaths != null) {
1818
            tree.setSelectionPaths(origSelectionPaths);
1819
        }
1820
1821
        remove(searchpanel);
1822
        searchpanel = null;
1823
        origSelectionPaths = null;
1824
        getViewport().setScrollMode(originalScrollMode);
1825
        invalidate();
1826
        revalidate();
1827
        repaint();
1828
    }
1719
    }
1829
1720
1830
    private class ExplorerScrollPaneLayout extends ScrollPaneLayout {
1721
    private class ExplorerScrollPaneLayout extends ScrollPaneLayout {
Lines 1832-1851 Link Here
1832
        @Override
1723
        @Override
1833
        public void layoutContainer( Container parent ) {
1724
        public void layoutContainer( Container parent ) {
1834
            super.layoutContainer(parent);
1725
            super.layoutContainer(parent);
1835
            if( null != searchpanel && searchpanel.isVisible() ) {
1726
            if( null != searchPanel && searchPanel.isVisible() ) {
1836
                Insets innerInsets = getInnerInsets();
1727
                Insets innerInsets = getInnerInsets();
1837
                Dimension prefSize = searchpanel.getPreferredSize();
1728
                Dimension prefSize = searchPanel.getPreferredSize();
1838
                searchpanel.setBounds(innerInsets.left, parent.getHeight()-innerInsets.bottom-prefSize.height,
1729
                searchPanel.setBounds(innerInsets.left, parent.getHeight()-innerInsets.bottom-prefSize.height,
1839
                        parent.getWidth()-innerInsets.left-innerInsets.right, prefSize.height);
1730
                        parent.getWidth()-innerInsets.left-innerInsets.right, prefSize.height);
1840
            }
1731
            }
1841
        }
1732
        }
1842
    }
1733
    }
1843
1734
1844
    private final class ExplorerTree extends JTree implements Autoscroll {
1735
    private final class ExplorerTree extends JTree implements Autoscroll, QuickSearchListener {
1845
        AutoscrollSupport support;
1736
        AutoscrollSupport support;
1846
        private String maxPrefix;
1737
        private String maxPrefix;
1847
        int SEARCH_FIELD_SPACE = 3;
1738
        int SEARCH_FIELD_SPACE = 3;
1848
        private boolean firstPaint = true;
1739
        private boolean firstPaint = true;
1740
        /** The last search searchResults */
1741
        private List<TreePath> searchResults = new ArrayList<TreePath>();
1742
        /** The last selected index from the search searchResults. */
1743
        private int currentSelectionIndex;
1744
        private String lastSearchText;
1849
1745
1850
1746
1851
        ExplorerTree(TreeModel model) {
1747
        ExplorerTree(TreeModel model) {
Lines 2008-2013 Link Here
2008
            new GuardedActions(3, fe);
1904
            new GuardedActions(3, fe);
2009
        }
1905
        }
2010
1906
1907
        @Override
1908
        protected void processKeyEvent(KeyEvent e) {
1909
            qs.processKeyEvent(e);
1910
            if (!e.isConsumed()) {
1911
                super.processKeyEvent(e);
1912
            }
1913
        }
1914
        
2011
        private void repaintSelection() {
1915
        private void repaintSelection() {
2012
            int first = getSelectionModel().getMinSelectionRow();
1916
            int first = getSelectionModel().getMinSelectionRow();
2013
            int last = getSelectionModel().getMaxSelectionRow();
1917
            int last = getSelectionModel().getMaxSelectionRow();
Lines 2039-2096 Link Here
2039
1943
2040
        private void setupSearch() {
1944
        private void setupSearch() {
2041
            // Remove the default key listeners
1945
            // Remove the default key listeners
2042
            KeyListener[] keyListeners = getListeners(KeyListener.class);
1946
//            KeyListener[] keyListeners = getListeners(KeyListener.class);
2043
1947
//
2044
            for (int i = 0; i < keyListeners.length; i++) {
1948
//            for (int i = 0; i < keyListeners.length; i++) {
2045
                removeKeyListener(keyListeners[i]);
1949
//                removeKeyListener(keyListeners[i]);
2046
            }
1950
//            }
2047
1951
            
2048
            // create new key listeners
1952
            qs = QuickSearch.attach(TreeView.this, searchConstraints);
2049
            quickSearchKeyAdapter = (
1953
            qs.addQuickSearchListener(this);
2050
                new KeyAdapter() {
2051
                @Override
2052
                    public void keyTyped(KeyEvent e) {
2053
                        int modifiers = e.getModifiers();
2054
                        int keyCode = e.getKeyCode();
2055
                        char c = e.getKeyChar();
2056
2057
                        //#43617 - don't eat + and -
2058
                        //#98634 - and all its duplicates dont't react to space
2059
                        if ((c == '+') || (c == '-') || (c==' ')) return; // NOI18N
2060
2061
                        if (((modifiers > 0) && (modifiers != KeyEvent.SHIFT_MASK)) || e.isActionKey()) {
2062
                            return;
2063
                        }
2064
2065
                        if (Character.isISOControl(c) ||
2066
                              (keyCode == KeyEvent.VK_SHIFT) ||
2067
			      (keyCode == KeyEvent.VK_ESCAPE)) return;
2068
2069
                        final KeyStroke stroke = KeyStroke.getKeyStrokeForEvent(e);
2070
                        origSelectionPaths = getSelectionPaths();
2071
                        if (origSelectionPaths != null && origSelectionPaths.length == 0) {
2072
                            origSelectionPaths = null;
2073
                        }
2074
                        searchTextField.setText(String.valueOf(stroke.getKeyChar()));
2075
2076
                        displaySearchField();
2077
                        e.consume();
2078
                    }
2079
                }
2080
            );
2081
            if(isQuickSearchAllowed()){
2082
                addKeyListener(quickSearchKeyAdapter);
2083
            }
2084
            // Create a the "multi-event" listener for the text field. Instead of
2085
            // adding separate instances of each needed listener, we're using a
2086
            // class which implements them all. This approach is used in order 
2087
            // to avoid the creation of 4 instances which takes some time
2088
            SearchFieldListener searchFieldListener = new SearchFieldListener();
2089
            searchTextField.addKeyListener(searchFieldListener);
2090
            searchTextField.addFocusListener(searchFieldListener);
2091
            searchTextField.getDocument().addDocumentListener(searchFieldListener);
2092
        }
1954
        }
2093
        
1955
        
1956
        @Override
1957
        public void quickSearchUpdate(String searchText) {
1958
            lastSearchText = searchText;
1959
            currentSelectionIndex = 0;
1960
            searchResults.clear();
1961
            maxPrefix = null;
1962
1963
            String text = searchText.toUpperCase();
1964
1965
            if (text.length() > 0) {
1966
                searchResults = doSearch(text);
1967
            }
1968
            displaySearchResult();
1969
        }
1970
1971
        @Override
1972
        public void showNextSelection(boolean forward) {
1973
            if (forward) {
1974
                currentSelectionIndex++;
1975
            } else {
1976
                currentSelectionIndex--;
1977
            }
1978
            displaySearchResult();
1979
        }
1980
1981
        @Override
1982
        public String findMaxPrefix(String prefix) {
1983
            return maxPrefix;
1984
        }
1985
1986
        @Override
1987
        public void quickSearchConfirmed() {
1988
            TreePath selectedTPath = getSelectionPath();
1989
            if (selectedTPath != null) {
1990
                TreeNode selectedTNode = (TreeNode) selectedTPath.getLastPathComponent();
1991
                Node selectedNode = Visualizer.findNode(selectedTNode);
1992
                performPreferredActionOnNodes(new Node[] { selectedNode });
1993
            }
1994
            origSelectionPaths = null;
1995
            searchResults.clear();
1996
            lastSearchText = null;
1997
        }
1998
1999
        @Override
2000
        public void quickSearchCanceled() {
2001
            origSelectionPaths = null;
2002
            searchResults.clear();
2003
            lastSearchText = null;
2004
        }
2005
2094
        private List<TreePath> doSearch(String prefix) {
2006
        private List<TreePath> doSearch(String prefix) {
2095
            List<TreePath> results = new ArrayList<TreePath>();
2007
            List<TreePath> results = new ArrayList<TreePath>();
2096
            Set<TreePath> resSet = new HashSet<TreePath>();
2008
            Set<TreePath> resSet = new HashSet<TreePath>();
Lines 2109-2115 Link Here
2109
            while (true) {
2021
            while (true) {
2110
                startIndex = startIndex % size;
2022
                startIndex = startIndex % size;
2111
2023
2112
                SubstringSearchResult substringSearchResult = getNextSubstringMatch(prefix, startIndex, Position.Bias.Forward);
2024
                SubstringSearchResult substringSearchResult = getNextSubstringMatch(prefix, startIndex, true);
2113
                TreePath path = substringSearchResult != null? substringSearchResult.treePath: null;
2025
                TreePath path = substringSearchResult != null? substringSearchResult.treePath: null;
2114
2026
2115
                if ((path != null) && !resSet.contains(path)) {
2027
                if ((path != null) && !resSet.contains(path)) {
Lines 2134-2140 Link Here
2134
                            maxPrefix = elementName;
2046
                            maxPrefix = elementName;
2135
                        }
2047
                        }
2136
2048
2137
                        maxPrefix = findMaxPrefix(maxPrefix, elementName);
2049
                        maxPrefix = QuickSearch.findMaxPrefix(maxPrefix, elementName, true);
2138
                    }
2050
                    }
2139
                    // try next element
2051
                    // try next element
2140
                    startIndex++;
2052
                    startIndex++;
Lines 2146-2161 Link Here
2146
            return results;
2058
            return results;
2147
        }
2059
        }
2148
2060
2149
        private String findMaxPrefix(String str1, String str2) {
2150
            String res = null;
2151
2152
            for (int i = 0; str1.regionMatches(true, 0, str2, 0, i); i++) {
2153
                res = str1.substring(0, i);
2154
            }
2155
2156
            return res;
2157
        }
2158
        
2159
        /**
2061
        /**
2160
         * Copied and adapted from JTree.getNextMatch(...).
2062
         * Copied and adapted from JTree.getNextMatch(...).
2161
         * 
2063
         * 
Lines 2163-2169 Link Here
2163
         *         and the index of the first occurrence of the substring in TreePath.
2065
         *         and the index of the first occurrence of the substring in TreePath.
2164
         */
2066
         */
2165
        private SubstringSearchResult getNextSubstringMatch(
2067
        private SubstringSearchResult getNextSubstringMatch(
2166
                String substring, int startingRow, Position.Bias bias) {
2068
                String substring, int startingRow, boolean forward) {
2167
2069
2168
            int max = getRowCount();
2070
            int max = getRowCount();
2169
            if (substring == null) {
2071
            if (substring == null) {
Lines 2176-2182 Link Here
2176
2078
2177
            // start search from the next/previous element froom the 
2079
            // start search from the next/previous element froom the 
2178
            // selected element
2080
            // selected element
2179
            int increment = (bias == Position.Bias.Forward) ? 1 : -1;
2081
            int increment = (forward) ? 1 : -1;
2180
            int row = startingRow;
2082
            int row = startingRow;
2181
            do {
2083
            do {
2182
                TreePath path = getPathForRow(row);
2084
                TreePath path = getPathForRow(row);
Lines 2193-2198 Link Here
2193
            return null;
2095
            return null;
2194
        }
2096
        }
2195
2097
2098
        private void displaySearchResult() {
2099
            int sz = searchResults.size();
2100
2101
            if (sz > 0) {
2102
                if (currentSelectionIndex < 0) {
2103
                    currentSelectionIndex = sz - 1;
2104
                } else if (currentSelectionIndex >= sz) {
2105
                    currentSelectionIndex = 0;
2106
                }
2107
2108
                TreePath path = searchResults.get(currentSelectionIndex);
2109
                setSelectionPath(path);
2110
                scrollPathToVisible(path);
2111
            } else {
2112
                if (lastSearchText.isEmpty() && origSelectionPaths != null) {
2113
                    setSelectionPaths(origSelectionPaths);
2114
                    scrollPathToVisible(origSelectionPaths[0]);
2115
                } else {
2116
                    clearSelection();
2117
                }
2118
            }
2119
        }
2120
2196
        /** notify the Component to autoscroll */
2121
        /** notify the Component to autoscroll */
2197
        @Override
2122
        @Override
2198
        public void autoscroll(Point cursorLoc) {
2123
        public void autoscroll(Point cursorLoc) {
Lines 2295-2425 Link Here
2295
            }
2220
            }
2296
        }
2221
        }
2297
2222
2298
        private class SearchFieldListener extends KeyAdapter implements DocumentListener, FocusListener {
2299
            /** The last search results */
2300
            private List<TreePath> results = new ArrayList<TreePath>();
2301
2302
            /** The last selected index from the search results. */
2303
            private int currentSelectionIndex;
2304
2305
            SearchFieldListener() {
2306
            }
2307
2308
            @Override
2309
            public void changedUpdate(DocumentEvent e) {
2310
                searchForNode();
2311
            }
2312
2313
            @Override
2314
            public void insertUpdate(DocumentEvent e) {
2315
                searchForNode();
2316
            }
2317
2318
            @Override
2319
            public void removeUpdate(DocumentEvent e) {
2320
                searchForNode();
2321
            }
2322
2323
            @Override
2324
            public void keyPressed(KeyEvent e) {
2325
                int keyCode = e.getKeyCode();
2326
2327
                if (keyCode == KeyEvent.VK_ESCAPE) {
2328
                    removeSearchField();
2329
                    ExplorerTree.this.requestFocus();
2330
                } else if (keyCode == KeyEvent.VK_UP || (keyCode == KeyEvent.VK_F3 && e.isShiftDown())) {
2331
                    currentSelectionIndex--;
2332
                    displaySearchResult();
2333
2334
                    // Stop processing the event here. Otherwise it's dispatched
2335
                    // to the tree too (which scrolls)
2336
                    e.consume();
2337
                } else if (keyCode == KeyEvent.VK_DOWN || keyCode == KeyEvent.VK_F3) {
2338
                    currentSelectionIndex++;
2339
                    displaySearchResult();
2340
2341
                    // Stop processing the event here. Otherwise it's dispatched
2342
                    // to the tree too (which scrolls)
2343
                    e.consume();
2344
                } else if (keyCode == KeyEvent.VK_TAB) {
2345
                    if (maxPrefix != null) {
2346
                        searchTextField.setText(maxPrefix);
2347
                    }
2348
2349
                    e.consume();
2350
                } else if (keyCode == KeyEvent.VK_ENTER) {
2351
                    removeSearchField();
2352
2353
                    // bugfix #39607, don't expand selected node when default action invoked
2354
                    TreePath selectedTPath = getSelectionPath();
2355
2356
                    if (selectedTPath != null) {
2357
                        TreeNode selectedTNode = (TreeNode) selectedTPath.getLastPathComponent();
2358
                        Node selectedNode = Visualizer.findNode(selectedTNode);
2359
2360
                        if (
2361
                            (selectedNode.getPreferredAction() == null) ||
2362
                                !selectedNode.getPreferredAction().isEnabled()
2363
                        ) {
2364
                            expandPath(getSelectionPath());
2365
                        }
2366
                    }
2367
2368
                    ExplorerTree.this.requestFocus();
2369
                    ExplorerTree.this.dispatchEvent(e);
2370
                }
2371
            }
2372
2373
            /** Searches for a node in the tree. */
2374
            private void searchForNode() {
2375
                currentSelectionIndex = 0;
2376
                results.clear();
2377
                maxPrefix = null;
2378
2379
                String text = searchTextField.getText().toUpperCase();
2380
2381
                if (text.length() > 0) {
2382
                    results = doSearch(text);
2383
                }
2384
                displaySearchResult();
2385
            }
2386
2387
            private void displaySearchResult() {
2388
                int sz = results.size();
2389
2390
                if (sz > 0) {
2391
                    if (currentSelectionIndex < 0) {
2392
                        currentSelectionIndex = sz - 1;
2393
                    } else if (currentSelectionIndex >= sz) {
2394
                        currentSelectionIndex = 0;
2395
                    }
2396
2397
                    TreePath path = results.get(currentSelectionIndex);
2398
                    setSelectionPath(path);
2399
                    scrollPathToVisible(path);
2400
                } else {
2401
                    if (searchTextField.getText().length() == 0 && origSelectionPaths != null) {
2402
                        setSelectionPaths(origSelectionPaths);
2403
                        scrollPathToVisible(origSelectionPaths[0]);
2404
                    } else {
2405
                        clearSelection();
2406
                    }
2407
                }
2408
            }
2409
2410
            @Override
2411
            public void focusGained(FocusEvent e) {
2412
                // make sure nothing is selected
2413
                searchTextField.select(1, 1);
2414
            }
2415
2416
            @Override
2417
            public void focusLost(FocusEvent e) {
2418
                results.clear();
2419
                removeSearchField();
2420
            }
2421
        }
2422
2423
        private class AccessibleExplorerTree extends JTree.AccessibleJTree {
2223
        private class AccessibleExplorerTree extends JTree.AccessibleJTree {
2424
            AccessibleExplorerTree() {
2224
            AccessibleExplorerTree() {
2425
            }
2225
            }
(-)a/openide.explorer/test/unit/src/org/openide/explorer/view/TreeViewQuickSearchTest.java (-2 / +2 lines)
Lines 219-227 Link Here
219
219
220
                if (phase[0] != 0) {
220
                if (phase[0] != 0) {
221
                    if (btv.isQuickSearchAllowed()) {
221
                    if (btv.isQuickSearchAllowed()) {
222
                        assertNotNull("Quick Search enabled ", btv.searchpanel);
222
                        assertNotNull("Quick Search enabled ", btv.getSearchPanel());
223
                    } else {
223
                    } else {
224
                        assertNull("Quick Search disable", btv.searchpanel);
224
                        assertNull("Quick Search disable", btv.getSearchPanel());
225
                    }
225
                    }
226
                }
226
                }
227
            }
227
            }

Return to bug 208794