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

Return to bug 208794