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 |
} |