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/spi.quicksearch/manifest.mf (-1 / +1 lines)
Lines 2-6 Link Here
2
OpenIDE-Module: org.netbeans.spi.quicksearch
2
OpenIDE-Module: org.netbeans.spi.quicksearch
3
OpenIDE-Module-Layer: org/netbeans/modules/quicksearch/resources/layer.xml
3
OpenIDE-Module-Layer: org/netbeans/modules/quicksearch/resources/layer.xml
4
OpenIDE-Module-Localizing-Bundle: org/netbeans/spi/quicksearch/Bundle.properties
4
OpenIDE-Module-Localizing-Bundle: org/netbeans/spi/quicksearch/Bundle.properties
5
OpenIDE-Module-Specification-Version: 1.13
5
OpenIDE-Module-Specification-Version: 1.14
6
6
(-)a/spi.quicksearch/nbproject/project.xml (+11 lines)
Lines 70-78 Link Here
70
                        <recursive/>
70
                        <recursive/>
71
                        <compile-dependency/>
71
                        <compile-dependency/>
72
                    </test-dependency>
72
                    </test-dependency>
73
                    <test-dependency>
74
                        <code-name-base>org.openide.modules</code-name-base>
75
                        <recursive/>
76
                        <compile-dependency/>
77
                    </test-dependency>
78
                    <test-dependency>
79
                        <code-name-base>org.netbeans.bootstrap</code-name-base>
80
                        <recursive/>
81
                        <compile-dependency/>
82
                    </test-dependency>
73
                </test-type>
83
                </test-type>
74
            </test-dependencies>
84
            </test-dependencies>
75
            <public-packages>
85
            <public-packages>
86
                <package>org.netbeans.api.quicksearch</package>
76
                <package>org.netbeans.spi.quicksearch</package>
87
                <package>org.netbeans.spi.quicksearch</package>
77
            </public-packages>
88
            </public-packages>
78
        </data>
89
        </data>
(-)a/openide.explorer/src/org/openide/explorer/view/QuickSearch.java (-64 / +257 lines)
Lines 39-65 Link Here
39
 *
39
 *
40
 * Portions Copyrighted 2012 Sun Microsystems, Inc.
40
 * Portions Copyrighted 2012 Sun Microsystems, Inc.
41
 */
41
 */
42
package org.openide.explorer.view;
42
package org.netbeans.api.quicksearch;
43
43
44
import java.awt.*;
44
import java.awt.*;
45
import java.awt.event.*;
45
import java.awt.event.*;
46
import java.lang.ref.WeakReference;
46
import java.lang.ref.WeakReference;
47
import java.util.LinkedList;
47
import java.util.LinkedList;
48
import java.util.List;
48
import java.util.List;
49
import javax.activation.DataContentHandler;
50
import javax.activation.DataContentHandlerFactory;
49
import javax.swing.*;
51
import javax.swing.*;
50
import javax.swing.event.DocumentEvent;
52
import javax.swing.event.DocumentEvent;
51
import javax.swing.event.DocumentListener;
53
import javax.swing.event.DocumentListener;
52
import javax.swing.text.Position.Bias;
54
import javax.swing.text.Position.Bias;
55
import org.netbeans.modules.quicksearch.QuickSearchComboBar.AnimationTimer;
56
import org.openide.util.RequestProcessor;
53
57
54
/**
58
/**
55
 * Quick search infrastructure
59
 * Quick search infrastructure for an arbitrary component.
60
 * When quick search is attached to a component, it listens on key events going
61
 * to the component and displays a quick search field.
56
 * 
62
 * 
57
 * @author Martin Entlicher
63
 * @author Martin Entlicher
64
 * @since 1.14
58
 */
65
 */
59
class QuickSearch {
66
public class QuickSearch {
60
    
67
    
61
    private static final String ICON_FIND = "org/openide/explorer/view/find.png";
68
    private static final String ICON_FIND = "org/netbeans/modules/quicksearch/resources/find.png";      // NOI18N
62
    private static final String ICON_FIND_WITH_MENU = "org/openide/explorer/view/findMenu.png";
69
    private static final String ICON_FIND_WITH_MENU = "org/netbeans/modules/quicksearch/resources/findMenu.png"; // NOI18N
63
    
70
    
64
    private final JComponent component;
71
    private final JComponent component;
65
    private final Object constraints;
72
    private final Object constraints;
Lines 67-74 Link Here
67
    private final List<QuickSearchListener> listeners = new LinkedList<QuickSearchListener>();
74
    private final List<QuickSearchListener> listeners = new LinkedList<QuickSearchListener>();
68
    private SearchTextField searchTextField;
75
    private SearchTextField searchTextField;
69
    private KeyAdapter quickSearchKeyAdapter;
76
    private KeyAdapter quickSearchKeyAdapter;
77
    private SearchFieldListener searchFieldListener;
70
    private JPanel searchPanel;
78
    private JPanel searchPanel;
71
    private JMenu popupMenu;
79
    private JMenu popupMenu;
80
    private boolean asynchronous;
81
    private RequestProcessor rp;
82
    private static enum QS_FIRE { UPDATE, NEXT, MAX }
83
    private AnimationTimer animationTimer;
72
    
84
    
73
    private QuickSearch(JComponent component, Object constraints) {
85
    private QuickSearch(JComponent component, Object constraints) {
74
        this.component = component;
86
        this.component = component;
Lines 76-81 Link Here
76
        setUpSearch();
88
        setUpSearch();
77
    }
89
    }
78
    
90
    
91
    /**
92
     * Attach quick search to a component with given constraints.
93
     * It listens on key events going to the component and displays a quick search
94
     * field.
95
     * 
96
     * @param component The component to attach to
97
     * @param constraints The constraints that are used to add the search field to the component.
98
     * @return An instance of QuickSearch class.
99
     */
79
    public static QuickSearch attach(JComponent component, Object constraints) {
100
    public static QuickSearch attach(JComponent component, Object constraints) {
80
        Object qso = component.getClientProperty(QuickSearch.class.getName());
101
        Object qso = component.getClientProperty(QuickSearch.class.getName());
81
        if (qso instanceof QuickSearch) {
102
        if (qso instanceof QuickSearch) {
Lines 87-101 Link Here
87
        }
108
        }
88
    }
109
    }
89
    
110
    
111
    /**
112
     * Detach the quick search from the component it was attached to.
113
     */
90
    public void detach() {
114
    public void detach() {
91
        setEnabled(false);
115
        setEnabled(false);
92
        component.putClientProperty(QuickSearch.class.getName(), null);
116
        component.putClientProperty(QuickSearch.class.getName(), null);
93
    }
117
    }
94
    
118
    
119
    /**
120
     * Test whether the quick search is enabled. This is <code>true</code>
121
     * by default.
122
     * @return <code>true</code> when the quick search is enabled,
123
     *         <code>false</code> otherwise.
124
     */
95
    public boolean isEnabled() {
125
    public boolean isEnabled() {
96
        return enabled;
126
        return enabled;
97
    }
127
    }
98
    
128
    
129
    /**
130
     * Set the enabled state of the quick search.
131
     * This allows to activate/deactivate the quick search functionality.
132
     * @param enabled <code>true</code> to enable the quick search,
133
     *                <code>false</code> otherwise.
134
     */
99
    public void setEnabled(boolean enabled) {
135
    public void setEnabled(boolean enabled) {
100
        if (this.enabled == enabled) {
136
        if (this.enabled == enabled) {
101
            return ;
137
            return ;
Lines 104-165 Link Here
104
        if (enabled) {
140
        if (enabled) {
105
            component.addKeyListener(quickSearchKeyAdapter);
141
            component.addKeyListener(quickSearchKeyAdapter);
106
        } else {
142
        } else {
143
            removeSearchField();
107
            component.removeKeyListener(quickSearchKeyAdapter);
144
            component.removeKeyListener(quickSearchKeyAdapter);
108
        }
145
        }
109
    }
146
    }
110
    
147
    
148
    /**
149
     * Test whether the quick search notifies {@link QuickSearchListener}
150
     * asynchronously, or not.
151
     * By default, QuickSearchListener is notified synchronously on AWT thread.
152
     * If <code>true</code>, three methods of QuickSearchListener is notified asynchronously
153
     * on a {@link RequestProcessor}'s thread. These are
154
     * {@link QuickSearchListener#showNextSelection(javax.swing.text.Position.Bias)},
155
     * {@link QuickSearchListener#showNextSelection(javax.swing.text.Position.Bias)},
156
     * {@link QuickSearchListener#findMaxPrefix(java.lang.String)}.
157
     * 
158
     * @return <code>false</code> for synchronous notification,
159
     *         <code>true</code> for asynchronous.
160
     */
161
    public boolean isAsynchronous() {
162
        return asynchronous;
163
    }
164
    
165
    /**
166
     * Set the asynchronous mode for the quick search notifications.
167
     * @param asynchronous <code>false</code> for synchronous notification,
168
     *                     <code>true</code> for asynchronous.
169
     * @see #isAsynchronous()
170
     */
171
    public void setAsynchronous(boolean asynchronous) {
172
        if (this.asynchronous == asynchronous) {
173
            return ;
174
        }
175
        this.asynchronous = asynchronous;
176
        if (asynchronous) {
177
            rp = new RequestProcessor("Quick Search RP", 1); // NOI18N
178
        } else {
179
            rp = null;
180
        }
181
    }
182
    
183
    /**
184
     * Attach a {@link QuickSearchListener}.
185
     * Notifications from the quick search field submissions are send to this
186
     * listener.
187
     * @param qsl the QuickSearchListener to attach.
188
     */
111
    public void addQuickSearchListener(QuickSearchListener qsl) {
189
    public void addQuickSearchListener(QuickSearchListener qsl) {
112
        synchronized (listeners) {
190
        synchronized (listeners) {
113
            listeners.add(qsl);
191
            listeners.add(qsl);
114
        }
192
        }
115
    }
193
    }
116
    
194
    
195
    /**
196
     * Remove a {@link QuickSearchListener}.
197
     * @param qsl the QuickSearchListener to remove.
198
     */
117
    public void removeQuickSearchListener(QuickSearchListener qsl) {
199
    public void removeQuickSearchListener(QuickSearchListener qsl) {
118
        synchronized (listeners) {
200
        synchronized (listeners) {
119
            listeners.remove(qsl);
201
            listeners.remove(qsl);
120
        }
202
        }
121
    }
203
    }
122
    
204
    
205
    /**
206
     * Get the list of {@link QuickSearchListener}s attached to this quick search.
207
     * @return The list of quick search listeners.
208
     */
209
    public QuickSearchListener[] getQuickSearchListeners() {
210
        synchronized (listeners) {
211
            return listeners.toArray(new QuickSearchListener[] {});
212
        }
213
    }
214
    
215
    /**
216
     * Set a pop-up menu, that is displayed on the find icon, next to the search
217
     * field. This allows customization of the search criteria.
218
     * @param popupMenu The provider of pop-up menu, which is taken from
219
     *                  {@link JMenu#getPopupMenu()}.
220
     */
123
    public void setPopupMenu(JMenu popupMenu) {
221
    public void setPopupMenu(JMenu popupMenu) {
124
        this.popupMenu = popupMenu;
222
        this.popupMenu = popupMenu;
125
    }
223
    }
126
    
224
    
225
    /**
226
     * Process this key event in addition to the key events obtained from the
227
     * component we're attached to.
228
     * @param ke a key event to process.
229
     */
127
    public void processKeyEvent(KeyEvent ke) {
230
    public void processKeyEvent(KeyEvent ke) {
128
        switch(ke.getID()) {
231
        if (searchPanel != null) {
129
            case KeyEvent.KEY_PRESSED:
232
            searchTextField.setCaretPosition(searchTextField.getText().length());
130
                quickSearchKeyAdapter.keyPressed(ke);
233
            searchTextField.processKeyEvent(ke);
131
                break;
234
        } else {
132
            case KeyEvent.KEY_RELEASED:
235
            switch(ke.getID()) {
133
                quickSearchKeyAdapter.keyReleased(ke);
236
                case KeyEvent.KEY_PRESSED:
134
                break;
237
                    quickSearchKeyAdapter.keyPressed(ke);
135
            case KeyEvent.KEY_TYPED:
238
                    break;
136
                quickSearchKeyAdapter.keyTyped(ke);
239
                case KeyEvent.KEY_RELEASED:
137
                break;
240
                    quickSearchKeyAdapter.keyReleased(ke);
241
                    break;
242
                case KeyEvent.KEY_TYPED:
243
                    quickSearchKeyAdapter.keyTyped(ke);
244
                    break;
245
            }
138
        }
246
        }
139
    }
247
    }
140
    
248
    
141
    private QuickSearchListener[] getQuickSearchListeners() {
249
    private void fireQuickSearchUpdate(String searchText) {
142
        QuickSearchListener[] qsls;
250
        QuickSearchListener[] qsls = getQuickSearchListeners();
143
        synchronized (listeners) {
251
        if (asynchronous) {
144
            qsls = listeners.toArray(new QuickSearchListener[] {});
252
            rp.post(new LazyFire(QS_FIRE.UPDATE, qsls, searchText));
253
        } else {
254
            fireQuickSearchUpdate(qsls, searchText);
145
        }
255
        }
146
        return qsls;
147
    }
256
    }
148
    
257
    
149
    private void fireQuickSearchUpdate(String searchText) {
258
    private void fireQuickSearchUpdate(QuickSearchListener[] qsls, String searchText) {
150
        for (QuickSearchListener qsl : getQuickSearchListeners()) {
259
        for (QuickSearchListener qsl : qsls) {
151
            qsl.quickSearchUpdate(searchText);
260
            qsl.quickSearchUpdate(searchText);
152
        }
261
        }
153
    }
262
    }
154
    
263
    
155
    private void fireShowNextSelection(Bias bias) {
264
    private void fireShowNextSelection(Bias bias) {
156
        for (QuickSearchListener qsl : getQuickSearchListeners()) {
265
        QuickSearchListener[] qsls = getQuickSearchListeners();
266
        if (asynchronous) {
267
            rp.post(new LazyFire(QS_FIRE.NEXT, qsls, bias));
268
        } else {
269
            fireShowNextSelection(qsls, bias);
270
        }
271
    }
272
    
273
    private void fireShowNextSelection(QuickSearchListener[] qsls, Bias bias) {
274
        for (QuickSearchListener qsl : qsls) {
157
            qsl.showNextSelection(bias);
275
            qsl.showNextSelection(bias);
158
        }
276
        }
159
    }
277
    }
160
    
278
    
161
    private String findMaxPrefix(String prefix) {
279
    private void findMaxPrefix(String prefix, DataContentHandlerFactory newPrefixSetter) {
162
        for (QuickSearchListener qsl : getQuickSearchListeners()) {
280
        QuickSearchListener[] qsls = getQuickSearchListeners();
281
        if (asynchronous) {
282
            rp.post(new LazyFire(QS_FIRE.MAX, qsls, prefix, newPrefixSetter));
283
        } else {
284
            prefix = findMaxPrefix(qsls, prefix);
285
            newPrefixSetter.createDataContentHandler(prefix);
286
        }
287
    }
288
    
289
    private String findMaxPrefix(QuickSearchListener[] qsls, String prefix) {
290
        for (QuickSearchListener qsl : qsls) {
163
            prefix = qsl.findMaxPrefix(prefix);
291
            prefix = qsl.findMaxPrefix(prefix);
164
        }
292
        }
165
        return prefix;
293
        return prefix;
Lines 200-221 Link Here
200
                            (keyCode == KeyEvent.VK_SHIFT) ||
328
                            (keyCode == KeyEvent.VK_SHIFT) ||
201
                            (keyCode == KeyEvent.VK_ESCAPE)) return;
329
                            (keyCode == KeyEvent.VK_ESCAPE)) return;
202
330
331
                    displaySearchField();
332
                    
203
                    final KeyStroke stroke = KeyStroke.getKeyStrokeForEvent(e);
333
                    final KeyStroke stroke = KeyStroke.getKeyStrokeForEvent(e);
204
                    searchTextField.setText(String.valueOf(stroke.getKeyChar()));
334
                    searchTextField.setText(String.valueOf(stroke.getKeyChar()));
205
335
206
                    displaySearchField();
207
                    e.consume();
336
                    e.consume();
208
                }
337
                }
209
            }
338
            }
210
        );
339
        );
211
        if(isEnabled()){
340
        if (isEnabled()) {
212
            component.addKeyListener(quickSearchKeyAdapter);
341
            component.addKeyListener(quickSearchKeyAdapter);
213
        }
342
        }
214
        // Create a the "multi-event" listener for the text field. Instead of
343
        // Create a the "multi-event" listener for the text field. Instead of
215
        // adding separate instances of each needed listener, we're using a
344
        // adding separate instances of each needed listener, we're using a
216
        // class which implements them all. This approach is used in order 
345
        // class which implements them all. This approach is used in order 
217
        // to avoid the creation of 4 instances which takes some time
346
        // to avoid the creation of 4 instances which takes some time
218
        SearchFieldListener searchFieldListener = new SearchFieldListener();
347
        searchFieldListener = new SearchFieldListener();
219
        searchTextField.addKeyListener(searchFieldListener);
348
        searchTextField.addKeyListener(searchFieldListener);
220
        searchTextField.addFocusListener(searchFieldListener);
349
        searchTextField.addFocusListener(searchFieldListener);
221
        searchTextField.getDocument().addDocumentListener(searchFieldListener);
350
        searchTextField.getDocument().addDocumentListener(searchFieldListener);
Lines 226-244 Link Here
226
        if (searchPanel != null || !isEnabled()) {
355
        if (searchPanel != null || !isEnabled()) {
227
            return;
356
            return;
228
        }
357
        }
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();
358
        searchTextField.setOriginalFocusOwner();
239
        searchTextField.setFont(component.getFont());
359
        searchTextField.setFont(component.getFont());
240
        searchPanel = new SearchPanel();
360
        searchPanel = new SearchPanel();
241
        //JLabel lbl = new JLabel(NbBundle.getMessage(TreeView.class, "LBL_QUICKSEARCH")); //NOI18N
242
        final JLabel lbl;
361
        final JLabel lbl;
243
        if (popupMenu != null) {
362
        if (popupMenu != null) {
244
            lbl = new JLabel(org.openide.util.ImageUtilities.loadImageIcon(ICON_FIND_WITH_MENU, false));
363
            lbl = new JLabel(org.openide.util.ImageUtilities.loadImageIcon(ICON_FIND_WITH_MENU, false));
Lines 255-260 Link Here
255
        } else {
374
        } else {
256
            lbl = new JLabel(org.openide.util.ImageUtilities.loadImageIcon(ICON_FIND, false));
375
            lbl = new JLabel(org.openide.util.ImageUtilities.loadImageIcon(ICON_FIND, false));
257
        }
376
        }
377
        if (asynchronous) {
378
            animationTimer = new AnimationTimer(lbl, lbl.getIcon());
379
        } else {
380
            animationTimer = null;
381
        }
258
        searchPanel.setLayout(new BoxLayout(searchPanel, BoxLayout.X_AXIS));
382
        searchPanel.setLayout(new BoxLayout(searchPanel, BoxLayout.X_AXIS));
259
        searchPanel.add(lbl);
383
        searchPanel.add(lbl);
260
        searchPanel.add(searchTextField);
384
        searchPanel.add(searchTextField);
Lines 263-274 Link Here
263
        searchTextField.setMaximumSize(searchTextField.getPreferredSize());
387
        searchTextField.setMaximumSize(searchTextField.getPreferredSize());
264
        searchTextField.putClientProperty("JTextField.variant", "search"); //NOI18N
388
        searchTextField.putClientProperty("JTextField.variant", "search"); //NOI18N
265
        lbl.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5));
389
        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) {
390
        if (constraints == null) {
273
            component.add(searchPanel);
391
            component.add(searchPanel);
274
        } else {
392
        } else {
Lines 284-297 Link Here
284
        if (searchPanel == null) {
402
        if (searchPanel == null) {
285
            return;
403
            return;
286
        }
404
        }
287
        component.remove(searchPanel);
405
        if (animationTimer != null) {
406
            animationTimer.stopProgressAnimation();
407
        }
408
        Component sp = searchPanel;
288
        searchPanel = null;
409
        searchPanel = null;
289
        //getViewport().setScrollMode(originalScrollMode);
410
        component.remove(sp);
290
        component.invalidate();
411
        component.invalidate();
291
        component.revalidate();
412
        component.revalidate();
292
        component.repaint();
413
        component.repaint();
293
    }
414
    }
294
    
415
    
416
    /** Accessed from test. */
417
    JTextField getSearchField() {
418
        return searchTextField;
419
    }
420
    
421
    /**
422
     * Utility method, that finds a greatest common substring of two supplied
423
     * strings.
424
     * 
425
     * @param str1 The first string
426
     * @param str2 The second string
427
     * @param ignoreCase Whether to ignore case in the comparisons
428
     * @return The greatest common substring of the two strings.
429
     */
295
    public static String findMaxCommonSubstring(String str1, String str2, boolean ignoreCase) {
430
    public static String findMaxCommonSubstring(String str1, String str2, boolean ignoreCase) {
296
        int n1 = str1.length();
431
        int n1 = str1.length();
297
        int n2 = str2.length();
432
        int n2 = str2.length();
Lines 316-339 Link Here
316
        return str1.substring(0, i);
451
        return str1.substring(0, i);
317
    }
452
    }
318
453
319
    public static interface QuickSearchListener {
454
    private class LazyFire implements Runnable {
320
        
455
        
321
        void quickSearchUpdate(String searchText);
456
        private final QS_FIRE fire;
457
        private final QuickSearchListener[] qsls;
458
        private final String searchText;
459
        private final Bias bias;
460
        private final DataContentHandlerFactory newPrefixSetter;
322
        
461
        
323
        void showNextSelection(Bias bias);
462
        LazyFire(QS_FIRE fire, QuickSearchListener[] qsls, String searchText) {
463
            this(fire, qsls, searchText, null, null);
464
        }
324
        
465
        
325
        String findMaxPrefix(String prefix);
466
        LazyFire(QS_FIRE fire, QuickSearchListener[] qsls, Bias bias) {
467
            this(fire, qsls, null, bias);
468
        }
326
        
469
        
327
        void quickSearchConfirmed();
470
        LazyFire(QS_FIRE fire, QuickSearchListener[] qsls, String searchText, Bias bias) {
471
            this(fire, qsls, searchText, bias, null);
472
        }
328
        
473
        
329
        void quickSearchCanceled();
474
        LazyFire(QS_FIRE fire, QuickSearchListener[] qsls, String searchText,
475
                 DataContentHandlerFactory newPrefixSetter) {
476
            this(fire, qsls, searchText, null, newPrefixSetter);
477
        }
478
        
479
        LazyFire(QS_FIRE fire, QuickSearchListener[] qsls, String searchText, Bias bias,
480
                 DataContentHandlerFactory newPrefixSetter) {
481
            this.fire = fire;
482
            this.qsls = qsls;
483
            this.searchText = searchText;
484
            this.bias = bias;
485
            this.newPrefixSetter = newPrefixSetter;
486
            animationTimer.startProgressAnimation();
487
        }
330
488
489
        @Override
490
        public void run() {
491
            try {
492
            switch (fire) {
493
                case UPDATE:    fireQuickSearchUpdate(qsls, searchText);
494
                                break;
495
                case NEXT:      fireShowNextSelection(qsls, bias);
496
                                break;
497
                case MAX:       String mp = findMaxPrefix(qsls, searchText);
498
                                newPrefixSetter.createDataContentHandler(mp);
499
                                break;
500
            }
501
            } finally {
502
                animationTimer.stopProgressAnimation();
503
            }
504
        }
331
    }
505
    }
332
    
506
    
333
    private static class SearchPanel extends JPanel {
507
    private static class SearchPanel extends JPanel {
334
        
508
        
509
        public static final boolean isAquaLaF =
510
                "Aqua".equals(UIManager.getLookAndFeel().getID()); //NOI18N
511
    
335
        public SearchPanel() {
512
        public SearchPanel() {
336
            if (ViewUtil.isAquaLaF) {
513
            if (isAquaLaF) {
337
                setBorder(BorderFactory.createEmptyBorder(9,6,8,2));
514
                setBorder(BorderFactory.createEmptyBorder(9,6,8,2));
338
            } else {
515
            } else {
339
                setBorder(BorderFactory.createEmptyBorder(2,6,2,2));
516
                setBorder(BorderFactory.createEmptyBorder(2,6,2,2));
Lines 343-351 Link Here
343
520
344
        @Override
521
        @Override
345
        protected void paintComponent(Graphics g) {
522
        protected void paintComponent(Graphics g) {
346
            if (ViewUtil.isAquaLaF && g instanceof Graphics2D) {
523
            if (isAquaLaF && g instanceof Graphics2D) {
347
                Graphics2D g2d = (Graphics2D) g;
524
                Graphics2D g2d = (Graphics2D) g;
348
                g2d.setPaint(new GradientPaint(0, 0, UIManager.getColor("NbExplorerView.quicksearch.background.top"),
525
                g2d.setPaint(new GradientPaint(0, 0, UIManager.getColor("NbExplorerView.quicksearch.background.top"), //NOI18N
349
                        0, getHeight(), UIManager.getColor("NbExplorerView.quicksearch.background.bottom")));//NOI18N
526
                        0, getHeight(), UIManager.getColor("NbExplorerView.quicksearch.background.bottom")));//NOI18N
350
                g2d.fillRect(0, 0, getWidth(), getHeight());
527
                g2d.fillRect(0, 0, getWidth(), getHeight());
351
                g2d.setColor(UIManager.getColor("NbExplorerView.quicksearch.border")); //NOI18N
528
                g2d.setColor(UIManager.getColor("NbExplorerView.quicksearch.border")); //NOI18N
Lines 459-471 Link Here
459
                // to the tree too (which scrolls)
636
                // to the tree too (which scrolls)
460
                e.consume();
637
                e.consume();
461
            } else if (keyCode == KeyEvent.VK_TAB) {
638
            } else if (keyCode == KeyEvent.VK_TAB) {
462
                String maxPrefix = findMaxPrefix(searchTextField.getText());
639
                findMaxPrefix(searchTextField.getText(), new DataContentHandlerFactory() {
463
                ignoreEvents = true;
640
                    @Override
464
                try {
641
                    public DataContentHandler createDataContentHandler(final String maxPrefix) {
465
                    searchTextField.setText(maxPrefix);
642
                        if (!SwingUtilities.isEventDispatchThread()) {
466
                } finally {
643
                            SwingUtilities.invokeLater(new Runnable() {
467
                    ignoreEvents = false;
644
                                @Override
468
                }
645
                                public void run() {
646
                                    createDataContentHandler(maxPrefix);
647
                                }
648
                            });
649
                            return null;
650
                        }
651
                        ignoreEvents = true;
652
                        try {
653
                            searchTextField.setText(maxPrefix);
654
                        } finally {
655
                            ignoreEvents = false;
656
                        }
657
                        return null;
658
                    }
659
                });
469
660
470
                e.consume();
661
                e.consume();
471
            } else if (keyCode == KeyEvent.VK_ENTER) {
662
            } else if (keyCode == KeyEvent.VK_ENTER) {
Lines 506-513 Link Here
506
            if (oppositeComponent == searchTextField) {
697
            if (oppositeComponent == searchTextField) {
507
                return ;
698
                return ;
508
            }
699
            }
509
            removeSearchField();
700
            if (searchPanel != null) {
510
            fireQuickSearchConfirmed();
701
                removeSearchField();
702
                fireQuickSearchConfirmed();
703
            }
511
        }
704
        }
512
    }
705
    }
513
706
(-)a/spi.quicksearch/src/org/netbeans/api/quicksearch/QuickSearchListener.java (+102 lines)
Line 0 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.netbeans.api.quicksearch;
43
44
import javax.swing.text.Position.Bias;
45
46
/**
47
 * Listener that is notified with the submissions to the quick search field.
48
 * 
49
 * @author Martin Entlicher
50
 * @since 1.14
51
 */
52
public interface QuickSearchListener {
53
    
54
    /**
55
     * Called with an updated search text.
56
     * When {@link QuickSearch#isAsynchronous()} is <code>false</code>
57
     * it's called in AWT, otherwise, it's called in an RequestProcessor thread.
58
     * The client should update the visual representation of the search results
59
     * and then return.<p>
60
     * This method is called to initiate and update the search process.
61
     * @param searchText The new text to search for.
62
     */
63
    void quickSearchUpdate(String searchText);
64
65
    /**
66
     * Called to select a next occurrence of the search result.
67
     * When {@link QuickSearch#isAsynchronous()} is <code>false</code>
68
     * it's called in AWT, otherwise, it's called in an RequestProcessor thread.
69
     * The client should update the visual representation of the search results
70
     * and then return.<p>
71
     * @param bias The direction of the next search result.
72
     *             {@link Bias#Forward} for forward direction,
73
     *             {@link Bias#Backward} for backward direction.
74
     */
75
    void showNextSelection(Bias bias);
76
77
    /**
78
     * Find the maximum prefix among the search results, that starts with the provided string.
79
     * This method is called when user press TAB in the search field, to auto-complete
80
     * the maximum prefix.
81
     * When {@link QuickSearch#isAsynchronous()} is <code>false</code>
82
     * it's called in AWT, otherwise, it's called in an RequestProcessor thread.
83
     * Utility method {@link QuickSearch#findMaxCommonSubstring(java.lang.String, java.lang.String, boolean)}
84
     * can be used by the implementation.
85
     * @param prefix The prefix to start with
86
     * @return The maximum prefix.
87
     */
88
    String findMaxPrefix(String prefix);
89
90
    /**
91
     * Called when the quick search is confirmed by the user.
92
     * This method is called in AWT always.
93
     */
94
    void quickSearchConfirmed();
95
96
    /**
97
     * Called when the quick search is canceled by the user.
98
     * This method is called in AWT always.
99
     */
100
    void quickSearchCanceled();
101
102
}
(-)a/spi.quicksearch/src/org/netbeans/modules/quicksearch/QuickSearchComboBar.java (-26 / +56 lines)
Lines 46-54 Link Here
46
import java.awt.event.ActionEvent;
46
import java.awt.event.ActionEvent;
47
import java.awt.event.ActionListener;
47
import java.awt.event.ActionListener;
48
import java.awt.event.KeyEvent;
48
import java.awt.event.KeyEvent;
49
import javax.swing.Icon;
49
import javax.swing.ImageIcon;
50
import javax.swing.ImageIcon;
50
import javax.swing.InputMap;
51
import javax.swing.InputMap;
51
import javax.swing.JComponent;
52
import javax.swing.JComponent;
53
import javax.swing.JLabel;
52
import javax.swing.JTextArea;
54
import javax.swing.JTextArea;
53
import javax.swing.KeyStroke;
55
import javax.swing.KeyStroke;
54
import javax.swing.Timer;
56
import javax.swing.Timer;
Lines 63-93 Link Here
63
65
64
    private final ImageIcon findIcon = new javax.swing.ImageIcon(getClass().getResource("/org/netbeans/modules/quicksearch/resources/find.png")); // NOI18N
66
    private final ImageIcon findIcon = new javax.swing.ImageIcon(getClass().getResource("/org/netbeans/modules/quicksearch/resources/find.png")); // NOI18N
65
    /** Timer used for progress animation (see #143019).  */
67
    /** Timer used for progress animation (see #143019).  */
66
    private final Timer animationTimer = new Timer(100, new ActionListener() {
68
    private final AnimationTimer animationTimer;
67
68
        ImageIcon icons[];
69
        int index = 0;
70
71
        public void actionPerformed(ActionEvent e) {
72
            if (icons == null) {
73
                icons = new ImageIcon[8];
74
                for (int i = 0; i < 8; i++) {
75
                    icons[i] = ImageUtilities.loadImageIcon("org/netbeans/modules/quicksearch/resources/progress_" + i + ".png", false);  //NOI18N
76
                }
77
            }
78
            jLabel2.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1, 1, 6));
79
            jLabel2.setIcon(icons[index]);
80
            //mac os x
81
            jLabel2.repaint();
82
            
83
            index = (index + 1) % 8;
84
        }
85
    });
86
69
87
    public QuickSearchComboBar(KeyStroke ks) {
70
    public QuickSearchComboBar(KeyStroke ks) {
88
        super( ks );
71
        super( ks );
89
72
90
        initComponents();
73
        initComponents();
74
        animationTimer = new AnimationTimer(jLabel2, findIcon);
91
    }
75
    }
92
76
93
77
Lines 145-150 Link Here
145
        gridBagConstraints.weightx = 1.0;
129
        gridBagConstraints.weightx = 1.0;
146
        add(jPanel1, gridBagConstraints);
130
        add(jPanel1, gridBagConstraints);
147
    }
131
    }
132
    
133
    public final static class AnimationTimer {
134
        
135
        private final JLabel jLabel;
136
        private final Icon findIcon;
137
        private final Timer animationTimer;
138
        
139
        public AnimationTimer(final JLabel jLabel, Icon findIcon) {
140
            this.jLabel = jLabel;
141
            this.findIcon = findIcon;
142
            animationTimer = new Timer(100, new ActionListener() {
143
144
                ImageIcon icons[];
145
                int index = 0;
146
147
                @Override
148
                public void actionPerformed(ActionEvent e) {
149
                    if (icons == null) {
150
                        icons = new ImageIcon[8];
151
                        for (int i = 0; i < 8; i++) {
152
                            icons[i] = ImageUtilities.loadImageIcon("org/netbeans/modules/quicksearch/resources/progress_" + i + ".png", false);  //NOI18N
153
                        }
154
                    }
155
                    jLabel.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1, 1, 6));
156
                    jLabel.setIcon(icons[index]);
157
                    //mac os x
158
                    jLabel.repaint();
159
160
                    index = (index + 1) % 8;
161
                }
162
            });
163
        }
164
        
165
        public void startProgressAnimation() {
166
            if (animationTimer != null && !animationTimer.isRunning()) {
167
                animationTimer.start();
168
            }
169
        }
170
171
        public void stopProgressAnimation() {
172
            if (animationTimer != null && animationTimer.isRunning()) {
173
                animationTimer.stop();
174
                jLabel.setIcon(findIcon);
175
                jLabel.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1, 1, 1));
176
            }
177
        }
178
179
    }
148
180
149
    private void jLabel2MousePressed(java.awt.event.MouseEvent evt) {
181
    private void jLabel2MousePressed(java.awt.event.MouseEvent evt) {
150
        maybeShowPopup(evt);
182
        maybeShowPopup(evt);
Lines 176-191 Link Here
176
    }
208
    }
177
209
178
    void startProgressAnimation() {
210
    void startProgressAnimation() {
179
        if (animationTimer != null && !animationTimer.isRunning()) {
211
        if (animationTimer != null) {
180
            animationTimer.start();
212
            animationTimer.startProgressAnimation();
181
        }
213
        }
182
    }
214
    }
183
215
184
    void stopProgressAnimation() {
216
    void stopProgressAnimation() {
185
        if (animationTimer != null && animationTimer.isRunning()) {
217
        if (animationTimer != null) {
186
            animationTimer.stop();
218
            animationTimer.stopProgressAnimation();
187
            jLabel2.setIcon(findIcon);
188
            jLabel2.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1, 1, 1));
189
        }
219
        }
190
    }
220
    }
191
221
(-)a/spi.quicksearch/test/unit/src/org/netbeans/api/quicksearch/QuickSearchTest.java (+659 lines)
Line 0 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.netbeans.api.quicksearch;
43
44
import java.awt.Component;
45
import java.awt.KeyboardFocusManager;
46
import java.awt.event.KeyEvent;
47
import java.awt.event.KeyListener;
48
import java.lang.reflect.InvocationTargetException;
49
import java.lang.reflect.Method;
50
import java.util.ArrayList;
51
import java.util.List;
52
import javax.swing.JComponent;
53
import javax.swing.JFrame;
54
import javax.swing.SwingUtilities;
55
import javax.swing.text.Position.Bias;
56
import org.junit.After;
57
import org.junit.AfterClass;
58
import org.junit.Before;
59
import org.junit.Test;
60
import static org.junit.Assert.*;
61
import org.junit.BeforeClass;
62
63
/**
64
 * Test of QuickSearch.
65
 * 
66
 * @author Martin Entlicher
67
 */
68
public class QuickSearchTest {
69
    
70
    public QuickSearchTest() {
71
    }
72
73
    @BeforeClass
74
    public static void setUpClass() throws Exception {
75
    }
76
77
    @AfterClass
78
    public static void tearDownClass() throws Exception {
79
    }
80
    
81
    @Before
82
    public void setUp() {
83
    }
84
    
85
    @After
86
    public void tearDown() {
87
    }
88
    
89
    /**
90
     * Test of attach and detach methods, of class QuickSearch.
91
     */
92
    @Test
93
    public void testAttachDetach() {
94
        TestComponent component = new TestComponent();
95
        Object constraints = null;
96
        QuickSearch qs = QuickSearch.attach(component, constraints);
97
        assertEquals("One added key listener is expected after attach", 1, component.addedKeyListeners.size());
98
        assertTrue(qs.isEnabled());
99
        assertFalse(qs.isAsynchronous());
100
        qs.detach();
101
        assertEquals("No key listener is expected after detach", 0, component.addedKeyListeners.size());
102
    }
103
104
    /**
105
     * Test of isEnabled and setEnabled methods, of class QuickSearch.
106
     */
107
    @Test
108
    public void testIsEnabled() {
109
        TestComponent component = new TestComponent();
110
        Object constraints = null;
111
        QuickSearch qs = QuickSearch.attach(component, constraints);
112
        assertTrue(qs.isEnabled());
113
        qs.setEnabled(false);
114
        assertEquals("No key listener is expected after setEnabled(false)", 0, component.addedKeyListeners.size());
115
        assertFalse(qs.isEnabled());
116
        qs.setEnabled(true);
117
        assertTrue(qs.isEnabled());
118
        assertEquals("One added key listener is expected after setEnabled(true)", 1, component.addedKeyListeners.size());
119
        qs.detach();
120
    }
121
122
    /**
123
     * Test of the addition of quick search component.
124
     */
125
    @Test
126
    public void testQuickSearchAdd() {
127
        if (!SwingUtilities.isEventDispatchThread()) {
128
            try {
129
                SwingUtilities.invokeAndWait(new Runnable() {
130
                    @Override
131
                    public void run() {
132
                        testQuickSearchAdd();
133
                    }
134
                });
135
            } catch (InterruptedException iex) {
136
                fail("interrupted.");
137
            } catch (InvocationTargetException itex) {
138
                Throwable cause = itex.getCause();
139
                if (cause instanceof AssertionError) {
140
                    throw (AssertionError) cause;
141
                }
142
                itex.getCause().printStackTrace();
143
                throw new AssertionError(cause);
144
            }
145
            return;
146
        }
147
        TestComponent component = new TestComponent();
148
        Object constraints = null;
149
        QuickSearch qs = QuickSearch.attach(component, constraints);
150
        component.addNotify();
151
        KeyEvent ke = new KeyEvent(component, KeyEvent.KEY_TYPED, System.currentTimeMillis(), 0, KeyEvent.VK_UNDEFINED, 'A');
152
        //KeyboardFocusManager.getCurrentKeyboardFocusManager().setGlobalFocusOwner(component);
153
        try {
154
            Method setGlobalFocusOwner = KeyboardFocusManager.class.getDeclaredMethod("setGlobalFocusOwner", Component.class);
155
            setGlobalFocusOwner.setAccessible(true);
156
            setGlobalFocusOwner.invoke(KeyboardFocusManager.getCurrentKeyboardFocusManager(), component);
157
        } catch (Exception ex) {
158
            ex.printStackTrace();
159
            throw new AssertionError(ex);
160
        }
161
        component.dispatchEvent(ke);
162
        assertNotNull(component.added);
163
        assertNull(component.constraints);
164
        qs.detach();
165
        assertNull(component.added);
166
        
167
        constraints = new Object();
168
        qs = QuickSearch.attach(component, constraints);
169
        ke = new KeyEvent(component, KeyEvent.KEY_TYPED, System.currentTimeMillis(), 0, KeyEvent.VK_UNDEFINED, 'A');
170
        component.dispatchEvent(ke);
171
        assertNotNull(component.added);
172
        assertEquals(constraints, component.constraints);
173
        qs.detach();
174
        assertNull(component.added);
175
    }
176
    
177
    /**
178
     * Test of the quick search listener.
179
     */
180
    @Test
181
    public void testQuickSearchListener() {
182
        if (!SwingUtilities.isEventDispatchThread()) {
183
            try {
184
                SwingUtilities.invokeAndWait(new Runnable() {
185
                    @Override
186
                    public void run() {
187
                        testQuickSearchListener();
188
                    }
189
                });
190
            } catch (InterruptedException iex) {
191
                fail("interrupted.");
192
            } catch (InvocationTargetException itex) {
193
                Throwable cause = itex.getCause();
194
                if (cause instanceof AssertionError) {
195
                    throw (AssertionError) cause;
196
                }
197
                itex.getCause().printStackTrace();
198
                throw new AssertionError(cause);
199
            }
200
            return;
201
        }
202
        TestComponent component = new TestComponent();
203
        Object constraints = null;
204
        QuickSearch qs = QuickSearch.attach(component, constraints);
205
        component.addNotify();
206
        final String[] searchTextPtr = new String[] { null };
207
        final Bias[] biasPtr = new Bias[] { null };
208
        final boolean[] confirmedPtr = new boolean[] { false };
209
        final boolean[] canceledPtr = new boolean[] { false };
210
        QuickSearchListener qsl = new QuickSearchListener() {
211
212
            @Override
213
            public void quickSearchUpdate(String searchText) {
214
                assertTrue(SwingUtilities.isEventDispatchThread());
215
                searchTextPtr[0] = searchText;
216
            }
217
218
            @Override
219
            public void showNextSelection(Bias bias) {
220
                assertTrue(SwingUtilities.isEventDispatchThread());
221
                biasPtr[0] = bias;
222
            }
223
224
            @Override
225
            public String findMaxPrefix(String prefix) {
226
                assertTrue(SwingUtilities.isEventDispatchThread());
227
                return prefix + "endPrefix";
228
            }
229
230
            @Override
231
            public void quickSearchConfirmed() {
232
                assertTrue(SwingUtilities.isEventDispatchThread());
233
                confirmedPtr[0] = true;
234
            }
235
236
            @Override
237
            public void quickSearchCanceled() {
238
                assertTrue(SwingUtilities.isEventDispatchThread());
239
                canceledPtr[0] = true;
240
            }
241
        };
242
        qs.addQuickSearchListener(qsl);
243
        // Test that a key event passed to the component triggers the quick search:
244
        try {
245
            Method setGlobalFocusOwner = KeyboardFocusManager.class.getDeclaredMethod("setGlobalFocusOwner", Component.class);
246
            setGlobalFocusOwner.setAccessible(true);
247
            setGlobalFocusOwner.invoke(KeyboardFocusManager.getCurrentKeyboardFocusManager(), component);
248
        } catch (Exception ex) {
249
            ex.printStackTrace();
250
            throw new AssertionError(ex);
251
        }
252
        KeyEvent ke = new KeyEvent(component, KeyEvent.KEY_TYPED, System.currentTimeMillis(), 0, KeyEvent.VK_UNDEFINED, 'A');
253
        component.dispatchEvent(ke);
254
        assertEquals("A", qs.getSearchField().getText());
255
        assertEquals("A", searchTextPtr[0]);
256
        assertNull(biasPtr[0]);
257
        
258
        // Test that further key events passed to the quick search field trigger the quick search listener:
259
        qs.getSearchField().setCaretPosition(1);
260
        try {
261
            Method setGlobalFocusOwner = KeyboardFocusManager.class.getDeclaredMethod("setGlobalFocusOwner", Component.class);
262
            setGlobalFocusOwner.setAccessible(true);
263
            setGlobalFocusOwner.invoke(KeyboardFocusManager.getCurrentKeyboardFocusManager(), qs.getSearchField());
264
        } catch (Exception ex) {
265
            ex.printStackTrace();
266
            throw new AssertionError(ex);
267
        }
268
        ke = new KeyEvent(qs.getSearchField(), KeyEvent.KEY_TYPED, System.currentTimeMillis(), 0, KeyEvent.VK_UNDEFINED, 'b');
269
        qs.getSearchField().dispatchEvent(ke);
270
        assertEquals("Ab", searchTextPtr[0]);
271
        
272
        // Test the up/down keys resulting to selection navigation:
273
        ke = new KeyEvent(qs.getSearchField(), KeyEvent.KEY_PRESSED, System.currentTimeMillis(), 0, KeyEvent.VK_UP, (char) KeyEvent.VK_UP);
274
        qs.getSearchField().dispatchEvent(ke);
275
        assertEquals(Bias.Backward, biasPtr[0]);
276
        
277
        ke = new KeyEvent(qs.getSearchField(), KeyEvent.KEY_PRESSED, System.currentTimeMillis(), 0, KeyEvent.VK_DOWN, (char) KeyEvent.VK_DOWN);
278
        qs.getSearchField().dispatchEvent(ke);
279
        assertEquals(Bias.Forward, biasPtr[0]);
280
        
281
        // Test that tab adds max prefix:
282
        ke = new KeyEvent(qs.getSearchField(), KeyEvent.KEY_PRESSED, System.currentTimeMillis(), 0, KeyEvent.VK_TAB, '\t');
283
        qs.getSearchField().dispatchEvent(ke);
284
        assertEquals("AbendPrefix", qs.getSearchField().getText());
285
        
286
        // Test that we get no events when quick search listener is detached:
287
        qs.removeQuickSearchListener(qsl);
288
        qs.getSearchField().setCaretPosition(2);
289
        ke = new KeyEvent(qs.getSearchField(), KeyEvent.KEY_TYPED, System.currentTimeMillis(), 0, KeyEvent.VK_UNDEFINED, 'c');
290
        qs.getSearchField().dispatchEvent(ke);
291
        assertEquals("AbcendPrefix", qs.getSearchField().getText());
292
        assertEquals("Ab", searchTextPtr[0]);
293
        
294
        // Test the quick search confirmation on Enter key:
295
        qs.addQuickSearchListener(qsl);
296
        assertFalse(confirmedPtr[0]);
297
        ke = new KeyEvent(qs.getSearchField(), KeyEvent.KEY_PRESSED, System.currentTimeMillis(), 0, KeyEvent.VK_ENTER, '\n');
298
        qs.getSearchField().dispatchEvent(ke);
299
        assertTrue(confirmedPtr[0]);
300
        
301
        // Test the quick search cancel on ESC key:
302
        ke = new KeyEvent(component, KeyEvent.KEY_TYPED, System.currentTimeMillis(), 0, KeyEvent.VK_UNDEFINED, 'A');
303
        component.dispatchEvent(ke);
304
        assertEquals("A", searchTextPtr[0]);
305
        assertFalse(canceledPtr[0]);
306
        ke = new KeyEvent(qs.getSearchField(), KeyEvent.KEY_PRESSED, System.currentTimeMillis(), 0, KeyEvent.VK_ESCAPE, (char) 27);
307
        qs.getSearchField().dispatchEvent(ke);
308
        assertTrue(canceledPtr[0]);
309
    }
310
    
311
    enum sync { W, N } // Wait, Notify
312
313
    /**
314
     * Test of asynchronous calls, of class QuickSearch.
315
     */
316
    @Test
317
    public void testAsynchronous() {
318
        final TestComponent[] componentPtr = new TestComponent[] { null };
319
        final QuickSearch[] qsPtr = new QuickSearch[] { null };
320
        try {
321
            SwingUtilities.invokeAndWait(new Runnable() {
322
                @Override
323
                public void run() {
324
                    componentPtr[0] = new TestComponent();
325
                    qsPtr[0] = QuickSearch.attach(componentPtr[0], null);
326
                    componentPtr[0].addNotify();
327
                }
328
            });
329
        } catch (InterruptedException iex) {
330
            fail("interrupted.");
331
        } catch (InvocationTargetException itex) {
332
            Throwable cause = itex.getCause();
333
            if (cause instanceof AssertionError) {
334
                throw (AssertionError) cause;
335
            }
336
            itex.getCause().printStackTrace();
337
            throw new AssertionError(cause);
338
        }
339
        final String[] searchTextPtr = new String[] { null };
340
        final Bias[] biasPtr = new Bias[] { null };
341
        final Object findMaxPrefixLock = new Object();
342
        final boolean[] confirmedPtr = new boolean[] { false };
343
        final boolean[] canceledPtr = new boolean[] { false };
344
        final boolean[] asynchronousPtr = new boolean[] { false };
345
        final sync[] syncPtr = new sync[] { null };
346
        QuickSearchListener qsl = new QuickSearchListener() {
347
348
            @Override
349
            public void quickSearchUpdate(String searchText) {
350
                assertTrue(asynchronousPtr[0] != SwingUtilities.isEventDispatchThread());
351
                synchronized(searchTextPtr) {
352
                    if (syncPtr[0] == null) {
353
                        syncPtr[0] = sync.W;
354
                        // Wait for the notification first
355
                        try { searchTextPtr.wait(); } catch (InterruptedException iex) {}
356
                    }
357
                    searchTextPtr[0] = searchText;
358
                    searchTextPtr.notifyAll();
359
                    syncPtr[0] = null;
360
                }
361
            }
362
363
            @Override
364
            public void showNextSelection(Bias bias) {
365
                assertTrue(asynchronousPtr[0] != SwingUtilities.isEventDispatchThread());
366
                synchronized(biasPtr) {
367
                    if (syncPtr[0] == null) {
368
                        syncPtr[0] = sync.W;
369
                        // Wait for the notification first
370
                        try { biasPtr.wait(); } catch (InterruptedException iex) {}
371
                    }
372
                    biasPtr[0] = bias;
373
                    biasPtr.notifyAll();
374
                    syncPtr[0] = null;
375
                }
376
            }
377
378
            @Override
379
            public String findMaxPrefix(String prefix) {
380
                assertTrue(asynchronousPtr[0] != SwingUtilities.isEventDispatchThread());
381
                synchronized(findMaxPrefixLock) {
382
                    if (syncPtr[0] == null) {
383
                        syncPtr[0] = sync.W;
384
                        // Wait for the notification first
385
                        try { findMaxPrefixLock.wait(); } catch (InterruptedException iex) {}
386
                    }
387
                    prefix = prefix + "endPrefix";
388
                    findMaxPrefixLock.notifyAll();
389
                    syncPtr[0] = null;
390
                }
391
                return prefix;
392
            }
393
394
            @Override
395
            public void quickSearchConfirmed() {
396
                assertTrue(SwingUtilities.isEventDispatchThread());
397
                confirmedPtr[0] = true;
398
            }
399
400
            @Override
401
            public void quickSearchCanceled() {
402
                assertTrue(SwingUtilities.isEventDispatchThread());
403
                canceledPtr[0] = true;
404
            }
405
        };
406
        qsPtr[0].addQuickSearchListener(qsl);
407
        assertFalse(qsPtr[0].isAsynchronous());
408
        qsPtr[0].setAsynchronous(true);
409
        asynchronousPtr[0] = true;
410
        assertTrue(qsPtr[0].isAsynchronous());
411
        
412
        // Test that a key event passed to the component triggers the asynchronous quick search:
413
        try {
414
            SwingUtilities.invokeAndWait(new Runnable() {
415
                @Override
416
                public void run() {
417
                    try {
418
                        Method setGlobalFocusOwner = KeyboardFocusManager.class.getDeclaredMethod("setGlobalFocusOwner", Component.class);
419
                        setGlobalFocusOwner.setAccessible(true);
420
                        setGlobalFocusOwner.invoke(KeyboardFocusManager.getCurrentKeyboardFocusManager(), componentPtr[0]);
421
                    } catch (Exception ex) {
422
                        ex.printStackTrace();
423
                        throw new AssertionError(ex);
424
                    }
425
                    KeyEvent ke = new KeyEvent(componentPtr[0], KeyEvent.KEY_TYPED, System.currentTimeMillis(), 0, KeyEvent.VK_UNDEFINED, 'A');
426
                    componentPtr[0].dispatchEvent(ke);
427
                }
428
            });
429
        } catch (InterruptedException iex) {
430
            fail("interrupted.");
431
        } catch (InvocationTargetException itex) {
432
            Throwable cause = itex.getCause();
433
            if (cause instanceof AssertionError) {
434
                throw (AssertionError) cause;
435
            }
436
            itex.getCause().printStackTrace();
437
            throw new AssertionError(cause);
438
        }
439
        synchronized(searchTextPtr) {
440
            assertNull(searchTextPtr[0]);
441
            syncPtr[0] = sync.N;
442
            searchTextPtr.notifyAll();
443
            // Wait to set the value
444
            try { searchTextPtr.wait(); } catch (InterruptedException iex) {}
445
            assertEquals("A", searchTextPtr[0]);
446
        }
447
        
448
        // Test the up/down keys resulting to asynchronous selection navigation:
449
        try {
450
            SwingUtilities.invokeAndWait(new Runnable() {
451
                @Override
452
                public void run() {
453
                    KeyEvent ke = new KeyEvent(qsPtr[0].getSearchField(), KeyEvent.KEY_PRESSED, System.currentTimeMillis(), 0, KeyEvent.VK_UP, (char) KeyEvent.VK_UP);
454
                    qsPtr[0].getSearchField().dispatchEvent(ke);
455
456
                    ke = new KeyEvent(qsPtr[0].getSearchField(), KeyEvent.KEY_PRESSED, System.currentTimeMillis(), 0, KeyEvent.VK_DOWN, (char) KeyEvent.VK_DOWN);
457
                    qsPtr[0].getSearchField().dispatchEvent(ke);
458
                }
459
            });
460
        } catch (InterruptedException iex) {
461
            fail("interrupted.");
462
        } catch (InvocationTargetException itex) {
463
            Throwable cause = itex.getCause();
464
            if (cause instanceof AssertionError) {
465
                throw (AssertionError) cause;
466
            }
467
            itex.getCause().printStackTrace();
468
            throw new AssertionError(cause);
469
        }
470
        synchronized(biasPtr) {
471
            assertNull(biasPtr[0]);
472
            syncPtr[0] = sync.N;
473
            biasPtr.notifyAll();
474
            // Wait to set the value
475
            try { biasPtr.wait(); } catch (InterruptedException iex) {}
476
            assertEquals(Bias.Backward, biasPtr[0]);
477
        }
478
        synchronized(biasPtr) {
479
            assertEquals(Bias.Backward, biasPtr[0]);
480
            syncPtr[0] = sync.N;
481
            biasPtr.notifyAll();
482
            // Wait to set the value
483
            try { biasPtr.wait(); } catch (InterruptedException iex) {}
484
            assertEquals(Bias.Forward, biasPtr[0]);
485
        }
486
        
487
        // Test that tab adds max prefix asynchronously:
488
        try {
489
            SwingUtilities.invokeAndWait(new Runnable() {
490
                @Override
491
                public void run() {
492
                    KeyEvent ke = new KeyEvent(qsPtr[0].getSearchField(), KeyEvent.KEY_PRESSED, System.currentTimeMillis(), 0, KeyEvent.VK_TAB, '\t');
493
                    qsPtr[0].getSearchField().dispatchEvent(ke);
494
                }
495
            });
496
        } catch (InterruptedException iex) {
497
            fail("interrupted.");
498
        } catch (InvocationTargetException itex) {
499
            Throwable cause = itex.getCause();
500
            if (cause instanceof AssertionError) {
501
                throw (AssertionError) cause;
502
            }
503
            itex.getCause().printStackTrace();
504
            throw new AssertionError(cause);
505
        }
506
        synchronized(findMaxPrefixLock) {
507
            assertEquals("A", qsPtr[0].getSearchField().getText());
508
            syncPtr[0] = sync.N;
509
            findMaxPrefixLock.notifyAll();
510
            // Wait to set the value
511
            try { findMaxPrefixLock.wait(); } catch (InterruptedException iex) {}
512
            // Can not test it immediatelly, the text is updated in AWT
513
            // assertEquals("AendPrefix", qsPtr[0].getSearchField().getText());
514
        }
515
        try { Thread.sleep(200); } catch (InterruptedException iex) {}
516
        try {
517
            SwingUtilities.invokeAndWait(new Runnable() {
518
                @Override
519
                public void run() {
520
                    assertEquals("AendPrefix", qsPtr[0].getSearchField().getText());
521
                }
522
            });
523
        } catch (InterruptedException iex) {
524
            fail("interrupted.");
525
        } catch (InvocationTargetException itex) {
526
            Throwable cause = itex.getCause();
527
            if (cause instanceof AssertionError) {
528
                throw (AssertionError) cause;
529
            }
530
            itex.getCause().printStackTrace();
531
            throw new AssertionError(cause);
532
        }
533
    }
534
    
535
    /**
536
     * Test of processKeyEvent method, of class QuickSearch.
537
     */
538
    @Test
539
    public void testProcessKeyEvent() {
540
        TestComponent component = new TestComponent();
541
        Object constraints = null;
542
        QuickSearch qs = QuickSearch.attach(component, constraints);
543
        final String[] searchTextPtr = new String[] { null };
544
        final Bias[] biasPtr = new Bias[] { null };
545
        final boolean[] confirmedPtr = new boolean[] { false };
546
        final boolean[] canceledPtr = new boolean[] { false };
547
        QuickSearchListener qsl = new QuickSearchListener() {
548
549
            @Override
550
            public void quickSearchUpdate(String searchText) {
551
                searchTextPtr[0] = searchText;
552
            }
553
554
            @Override
555
            public void showNextSelection(Bias bias) {
556
                biasPtr[0] = bias;
557
            }
558
559
            @Override
560
            public String findMaxPrefix(String prefix) {
561
                return prefix + "endPrefix";
562
            }
563
564
            @Override
565
            public void quickSearchConfirmed() {
566
                confirmedPtr[0] = true;
567
            }
568
569
            @Override
570
            public void quickSearchCanceled() {
571
                canceledPtr[0] = true;
572
            }
573
        };
574
        qs.addQuickSearchListener(qsl);
575
        KeyEvent ke = new KeyEvent(component, KeyEvent.KEY_TYPED, System.currentTimeMillis(), 0, KeyEvent.VK_UNDEFINED, 'A');
576
        qs.processKeyEvent(ke);
577
        assertEquals("A", qs.getSearchField().getText());
578
        assertEquals("A", searchTextPtr[0]);
579
        assertNull(biasPtr[0]);
580
        
581
        ke = new KeyEvent(qs.getSearchField(), KeyEvent.KEY_TYPED, System.currentTimeMillis(), 0, KeyEvent.VK_UNDEFINED, 'b');
582
        qs.processKeyEvent(ke);
583
        assertEquals("Ab", qs.getSearchField().getText());
584
        assertEquals("Ab", searchTextPtr[0]);
585
    }
586
587
    /**
588
     * Test of findMaxCommonSubstring method, of class QuickSearch.
589
     */
590
    @Test
591
    public void testFindMaxCommonSubstring() {
592
        System.out.println("findMaxCommonSubstring");
593
        String str1 = "annotation";
594
        String str2 = "antenna";
595
        boolean ignoreCase = false;
596
        String expResult = "an";
597
        String result = QuickSearch.findMaxCommonSubstring(str1, str2, ignoreCase);
598
        assertEquals(expResult, result);
599
        str1 = "Annotation";
600
        expResult = "";
601
        result = QuickSearch.findMaxCommonSubstring(str1, str2, ignoreCase);
602
        assertEquals(expResult, result);
603
        str1 = "AbCdEf";
604
        str2 = "AbCxxx";
605
        expResult = "AbC";
606
        result = QuickSearch.findMaxCommonSubstring(str1, str2, ignoreCase);
607
        assertEquals(expResult, result);
608
    }
609
    
610
    private static final class TestComponent extends JComponent {
611
        
612
        List<KeyListener> addedKeyListeners = new ArrayList<KeyListener>();
613
        Component added;
614
        Object constraints;
615
        
616
        public TestComponent() {
617
            new JFrame().add(this); // To have a parent
618
        }
619
620
        @Override
621
        public boolean isShowing() {
622
            return true;
623
        }
624
        
625
        @Override
626
        public Component add(Component comp) {
627
            this.added = comp;
628
            return super.add(comp);
629
        }
630
631
        @Override
632
        public void add(Component comp, Object constraints) {
633
            this.added = comp;
634
            this.constraints = constraints;
635
            super.add(comp, constraints);
636
        }
637
638
        @Override
639
        public void remove(Component comp) {
640
            if (comp == this.added) {
641
                this.added = null;
642
            }
643
            super.remove(comp);
644
        }
645
        
646
        @Override
647
        public synchronized void addKeyListener(KeyListener l) {
648
            addedKeyListeners.add(l);
649
            super.addKeyListener(l);
650
        }
651
652
        @Override
653
        public synchronized void removeKeyListener(KeyListener l) {
654
            addedKeyListeners.remove(l);
655
            super.removeKeyListener(l);
656
        }
657
        
658
    }
659
}

Return to bug 208794