Lines 39-65
Link Here
|
39 |
* |
39 |
* |
40 |
* Portions Copyrighted 2012 Sun Microsystems, Inc. |
40 |
* Portions Copyrighted 2012 Sun Microsystems, Inc. |
41 |
*/ |
41 |
*/ |
42 |
package org.openide.explorer.view; |
42 |
package org.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 |
63 |
|
72 |
|
64 |
private final JComponent component; |
73 |
private final JComponent component; |
65 |
private final Object constraints; |
74 |
private final Object constraints; |
Lines 67-74
Link Here
|
67 |
private final List<QuickSearchListener> listeners = new LinkedList<QuickSearchListener>(); |
76 |
private final List<QuickSearchListener> listeners = new LinkedList<QuickSearchListener>(); |
68 |
private SearchTextField searchTextField; |
77 |
private SearchTextField searchTextField; |
69 |
private KeyAdapter quickSearchKeyAdapter; |
78 |
private KeyAdapter quickSearchKeyAdapter; |
|
|
79 |
private SearchFieldListener searchFieldListener; |
70 |
private JPanel searchPanel; |
80 |
private JPanel searchPanel; |
71 |
private JMenu popupMenu; |
81 |
private JMenu popupMenu; |
|
|
82 |
private boolean asynchronous; |
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) { |
74 |
this.component = component; |
88 |
this.component = component; |
Lines 76-85
Link Here
|
76 |
setUpSearch(); |
90 |
setUpSearch(); |
77 |
} |
91 |
} |
78 |
|
92 |
|
|
|
93 |
/** |
94 |
* Attach quick search to a component with given constraints. |
95 |
* It listens on key events going to the component and displays a quick search |
96 |
* field. |
97 |
* |
98 |
* @param component The component to attach to |
99 |
* @param constraints The constraints that are used to add the search field |
100 |
* to the component. It's passed to {@link JComponent#add(java.awt.Component, java.lang.Object)} |
101 |
* when adding the quick search UI to the component. |
102 |
* @return An instance of QuickSearch class. |
103 |
*/ |
79 |
public static QuickSearch attach(JComponent component, Object constraints) { |
104 |
public static QuickSearch attach(JComponent component, Object constraints) { |
80 |
Object qso = component.getClientProperty(QuickSearch.class.getName()); |
105 |
Object qso = component.getClientProperty(QuickSearch.class.getName()); |
81 |
if (qso instanceof QuickSearch) { |
106 |
if (qso instanceof QuickSearch) { |
82 |
return (QuickSearch) qso; |
107 |
throw new IllegalStateException("A quick search is attached to this component already, detach it first."); // NOI18N |
83 |
} else { |
108 |
} else { |
84 |
QuickSearch qs = new QuickSearch(component, constraints); |
109 |
QuickSearch qs = new QuickSearch(component, constraints); |
85 |
component.putClientProperty(QuickSearch.class.getName(), qs); |
110 |
component.putClientProperty(QuickSearch.class.getName(), qs); |
Lines 87-101
Link Here
|
87 |
} |
112 |
} |
88 |
} |
113 |
} |
89 |
|
114 |
|
|
|
115 |
/** |
116 |
* Detach the quick search from the component it was attached to. |
117 |
*/ |
90 |
public void detach() { |
118 |
public void detach() { |
91 |
setEnabled(false); |
119 |
setEnabled(false); |
92 |
component.putClientProperty(QuickSearch.class.getName(), null); |
120 |
component.putClientProperty(QuickSearch.class.getName(), null); |
93 |
} |
121 |
} |
94 |
|
122 |
|
|
|
123 |
/** |
124 |
* Test whether the quick search is enabled. This is <code>true</code> |
125 |
* by default. |
126 |
* @return <code>true</code> when the quick search is enabled, |
127 |
* <code>false</code> otherwise. |
128 |
*/ |
95 |
public boolean isEnabled() { |
129 |
public boolean isEnabled() { |
96 |
return enabled; |
130 |
return enabled; |
97 |
} |
131 |
} |
98 |
|
132 |
|
|
|
133 |
/** |
134 |
* Set the enabled state of the quick search. |
135 |
* This allows to activate/deactivate the quick search functionality. |
136 |
* @param enabled <code>true</code> to enable the quick search, |
137 |
* <code>false</code> otherwise. |
138 |
*/ |
99 |
public void setEnabled(boolean enabled) { |
139 |
public void setEnabled(boolean enabled) { |
100 |
if (this.enabled == enabled) { |
140 |
if (this.enabled == enabled) { |
101 |
return ; |
141 |
return ; |
Lines 104-165
Link Here
|
104 |
if (enabled) { |
144 |
if (enabled) { |
105 |
component.addKeyListener(quickSearchKeyAdapter); |
145 |
component.addKeyListener(quickSearchKeyAdapter); |
106 |
} else { |
146 |
} else { |
|
|
147 |
removeSearchField(); |
107 |
component.removeKeyListener(quickSearchKeyAdapter); |
148 |
component.removeKeyListener(quickSearchKeyAdapter); |
108 |
} |
149 |
} |
109 |
} |
150 |
} |
110 |
|
151 |
|
|
|
152 |
/** |
153 |
* Test whether the quick search notifies {@link QuickSearchListener} |
154 |
* asynchronously, or not. |
155 |
* By default, QuickSearchListener is notified synchronously on EQ thread. |
156 |
* If <code>true</code>, three methods of QuickSearchListener are notified asynchronously |
157 |
* on a background thread. These are |
158 |
* {@link QuickSearchListener#quickSearchUpdate(java.lang.String)}, |
159 |
* {@link QuickSearchListener#showNextSelection(javax.swing.text.Position.Bias)}, |
160 |
* {@link QuickSearchListener#findMaxPrefix(java.lang.String)}. |
161 |
* |
162 |
* @return <code>false</code> for synchronous notification, |
163 |
* <code>true</code> for asynchronous. |
164 |
*/ |
165 |
public boolean isAsynchronous() { |
166 |
return asynchronous; |
167 |
} |
168 |
|
169 |
/** |
170 |
* Set the asynchronous mode for the quick search notifications. |
171 |
* @param asynchronous <code>false</code> for synchronous notification, |
172 |
* <code>true</code> for asynchronous. |
173 |
* @see #isAsynchronous() |
174 |
*/ |
175 |
public void setAsynchronous(boolean asynchronous) { |
176 |
if (this.asynchronous == asynchronous) { |
177 |
return ; |
178 |
} |
179 |
this.asynchronous = asynchronous; |
180 |
if (asynchronous) { |
181 |
rp = new RequestProcessor(QuickSearch.class); |
182 |
} else { |
183 |
rp = null; |
184 |
} |
185 |
} |
186 |
|
187 |
/** |
188 |
* Attach a {@link QuickSearchListener}. |
189 |
* Notifications from the quick search field submissions are send to this |
190 |
* listener. |
191 |
* @param qsl the QuickSearchListener to attach. |
192 |
*/ |
111 |
public void addQuickSearchListener(QuickSearchListener qsl) { |
193 |
public void addQuickSearchListener(QuickSearchListener qsl) { |
112 |
synchronized (listeners) { |
194 |
synchronized (listeners) { |
113 |
listeners.add(qsl); |
195 |
listeners.add(qsl); |
114 |
} |
196 |
} |
115 |
} |
197 |
} |
116 |
|
198 |
|
|
|
199 |
/** |
200 |
* Remove a {@link QuickSearchListener}. |
201 |
* @param qsl the QuickSearchListener to remove. |
202 |
*/ |
117 |
public void removeQuickSearchListener(QuickSearchListener qsl) { |
203 |
public void removeQuickSearchListener(QuickSearchListener qsl) { |
118 |
synchronized (listeners) { |
204 |
synchronized (listeners) { |
119 |
listeners.remove(qsl); |
205 |
listeners.remove(qsl); |
120 |
} |
206 |
} |
121 |
} |
207 |
} |
122 |
|
208 |
|
|
|
209 |
/** |
210 |
* Get the list of {@link QuickSearchListener}s attached to this quick search. |
211 |
* @return The list of quick search listeners. |
212 |
*/ |
213 |
public QuickSearchListener[] getQuickSearchListeners() { |
214 |
synchronized (listeners) { |
215 |
return listeners.toArray(new QuickSearchListener[] {}); |
216 |
} |
217 |
} |
218 |
|
219 |
/** |
220 |
* Set a pop-up menu, that is displayed on the find icon, next to the search |
221 |
* field. This allows customization of the search criteria. |
222 |
* @param popupMenu The provider of pop-up menu, which is taken from |
223 |
* {@link JMenu#getPopupMenu()}. |
224 |
*/ |
123 |
public void setPopupMenu(JMenu popupMenu) { |
225 |
public void setPopupMenu(JMenu popupMenu) { |
124 |
this.popupMenu = popupMenu; |
226 |
this.popupMenu = popupMenu; |
125 |
} |
227 |
} |
126 |
|
228 |
|
|
|
229 |
/** |
230 |
* Process this key event in addition to the key events obtained from the |
231 |
* component we're attached to. |
232 |
* @param ke a key event to process. |
233 |
*/ |
127 |
public void processKeyEvent(KeyEvent ke) { |
234 |
public void processKeyEvent(KeyEvent ke) { |
128 |
switch(ke.getID()) { |
235 |
if (searchPanel != null) { |
129 |
case KeyEvent.KEY_PRESSED: |
236 |
searchTextField.setCaretPosition(searchTextField.getText().length()); |
130 |
quickSearchKeyAdapter.keyPressed(ke); |
237 |
searchTextField.processKeyEvent(ke); |
131 |
break; |
238 |
} else { |
132 |
case KeyEvent.KEY_RELEASED: |
239 |
switch(ke.getID()) { |
133 |
quickSearchKeyAdapter.keyReleased(ke); |
240 |
case KeyEvent.KEY_PRESSED: |
134 |
break; |
241 |
quickSearchKeyAdapter.keyPressed(ke); |
135 |
case KeyEvent.KEY_TYPED: |
242 |
break; |
136 |
quickSearchKeyAdapter.keyTyped(ke); |
243 |
case KeyEvent.KEY_RELEASED: |
137 |
break; |
244 |
quickSearchKeyAdapter.keyReleased(ke); |
|
|
245 |
break; |
246 |
case KeyEvent.KEY_TYPED: |
247 |
quickSearchKeyAdapter.keyTyped(ke); |
248 |
break; |
249 |
} |
138 |
} |
250 |
} |
139 |
} |
251 |
} |
140 |
|
252 |
|
141 |
private QuickSearchListener[] getQuickSearchListeners() { |
253 |
private void fireQuickSearchUpdate(String searchText) { |
142 |
QuickSearchListener[] qsls; |
254 |
QuickSearchListener[] qsls = getQuickSearchListeners(); |
143 |
synchronized (listeners) { |
255 |
if (asynchronous) { |
144 |
qsls = listeners.toArray(new QuickSearchListener[] {}); |
256 |
rp.post(new LazyFire(QS_FIRE.UPDATE, qsls, searchText)); |
|
|
257 |
} else { |
258 |
fireQuickSearchUpdate(qsls, searchText); |
145 |
} |
259 |
} |
146 |
return qsls; |
|
|
147 |
} |
260 |
} |
148 |
|
261 |
|
149 |
private void fireQuickSearchUpdate(String searchText) { |
262 |
private void fireQuickSearchUpdate(QuickSearchListener[] qsls, String searchText) { |
150 |
for (QuickSearchListener qsl : getQuickSearchListeners()) { |
263 |
for (QuickSearchListener qsl : qsls) { |
151 |
qsl.quickSearchUpdate(searchText); |
264 |
qsl.quickSearchUpdate(searchText); |
152 |
} |
265 |
} |
153 |
} |
266 |
} |
154 |
|
267 |
|
155 |
private void fireShowNextSelection(Bias bias) { |
268 |
private void fireShowNextSelection(boolean forward) { |
156 |
for (QuickSearchListener qsl : getQuickSearchListeners()) { |
269 |
QuickSearchListener[] qsls = getQuickSearchListeners(); |
157 |
qsl.showNextSelection(bias); |
270 |
if (asynchronous) { |
|
|
271 |
rp.post(new LazyFire(QS_FIRE.NEXT, qsls, forward)); |
272 |
} else { |
273 |
fireShowNextSelection(qsls, forward); |
158 |
} |
274 |
} |
159 |
} |
275 |
} |
160 |
|
276 |
|
161 |
private String findMaxPrefix(String prefix) { |
277 |
private void fireShowNextSelection(QuickSearchListener[] qsls, boolean forward) { |
162 |
for (QuickSearchListener qsl : getQuickSearchListeners()) { |
278 |
for (QuickSearchListener qsl : qsls) { |
|
|
279 |
qsl.showNextSelection(forward); |
280 |
} |
281 |
} |
282 |
|
283 |
private void findMaxPrefix(String prefix, DataContentHandlerFactory newPrefixSetter) { |
284 |
QuickSearchListener[] qsls = getQuickSearchListeners(); |
285 |
if (asynchronous) { |
286 |
rp.post(new LazyFire(QS_FIRE.MAX, qsls, prefix, newPrefixSetter)); |
287 |
} else { |
288 |
prefix = findMaxPrefix(qsls, prefix); |
289 |
newPrefixSetter.createDataContentHandler(prefix); |
290 |
} |
291 |
} |
292 |
|
293 |
private String findMaxPrefix(QuickSearchListener[] qsls, String prefix) { |
294 |
for (QuickSearchListener qsl : qsls) { |
163 |
prefix = qsl.findMaxPrefix(prefix); |
295 |
prefix = qsl.findMaxPrefix(prefix); |
164 |
} |
296 |
} |
165 |
return prefix; |
297 |
return prefix; |
Lines 200-221
Link Here
|
200 |
(keyCode == KeyEvent.VK_SHIFT) || |
332 |
(keyCode == KeyEvent.VK_SHIFT) || |
201 |
(keyCode == KeyEvent.VK_ESCAPE)) return; |
333 |
(keyCode == KeyEvent.VK_ESCAPE)) return; |
202 |
|
334 |
|
|
|
335 |
displaySearchField(); |
336 |
|
203 |
final KeyStroke stroke = KeyStroke.getKeyStrokeForEvent(e); |
337 |
final KeyStroke stroke = KeyStroke.getKeyStrokeForEvent(e); |
204 |
searchTextField.setText(String.valueOf(stroke.getKeyChar())); |
338 |
searchTextField.setText(String.valueOf(stroke.getKeyChar())); |
205 |
|
339 |
|
206 |
displaySearchField(); |
|
|
207 |
e.consume(); |
340 |
e.consume(); |
208 |
} |
341 |
} |
209 |
} |
342 |
} |
210 |
); |
343 |
); |
211 |
if(isEnabled()){ |
344 |
if (isEnabled()) { |
212 |
component.addKeyListener(quickSearchKeyAdapter); |
345 |
component.addKeyListener(quickSearchKeyAdapter); |
213 |
} |
346 |
} |
214 |
// Create a the "multi-event" listener for the text field. Instead of |
347 |
// Create a the "multi-event" listener for the text field. Instead of |
215 |
// adding separate instances of each needed listener, we're using a |
348 |
// adding separate instances of each needed listener, we're using a |
216 |
// class which implements them all. This approach is used in order |
349 |
// class which implements them all. This approach is used in order |
217 |
// to avoid the creation of 4 instances which takes some time |
350 |
// to avoid the creation of 4 instances which takes some time |
218 |
SearchFieldListener searchFieldListener = new SearchFieldListener(); |
351 |
searchFieldListener = new SearchFieldListener(); |
219 |
searchTextField.addKeyListener(searchFieldListener); |
352 |
searchTextField.addKeyListener(searchFieldListener); |
220 |
searchTextField.addFocusListener(searchFieldListener); |
353 |
searchTextField.addFocusListener(searchFieldListener); |
221 |
searchTextField.getDocument().addDocumentListener(searchFieldListener); |
354 |
searchTextField.getDocument().addDocumentListener(searchFieldListener); |
Lines 226-244
Link Here
|
226 |
if (searchPanel != null || !isEnabled()) { |
359 |
if (searchPanel != null || !isEnabled()) { |
227 |
return; |
360 |
return; |
228 |
} |
361 |
} |
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(); |
362 |
searchTextField.setOriginalFocusOwner(); |
239 |
searchTextField.setFont(component.getFont()); |
363 |
searchTextField.setFont(component.getFont()); |
240 |
searchPanel = new SearchPanel(); |
364 |
searchPanel = new SearchPanel(); |
241 |
//JLabel lbl = new JLabel(NbBundle.getMessage(TreeView.class, "LBL_QUICKSEARCH")); //NOI18N |
|
|
242 |
final JLabel lbl; |
365 |
final JLabel lbl; |
243 |
if (popupMenu != null) { |
366 |
if (popupMenu != null) { |
244 |
lbl = new JLabel(org.openide.util.ImageUtilities.loadImageIcon(ICON_FIND_WITH_MENU, false)); |
367 |
lbl = new JLabel(org.openide.util.ImageUtilities.loadImageIcon(ICON_FIND_WITH_MENU, false)); |
Lines 255-260
Link Here
|
255 |
} else { |
378 |
} else { |
256 |
lbl = new JLabel(org.openide.util.ImageUtilities.loadImageIcon(ICON_FIND, false)); |
379 |
lbl = new JLabel(org.openide.util.ImageUtilities.loadImageIcon(ICON_FIND, false)); |
257 |
} |
380 |
} |
|
|
381 |
if (asynchronous) { |
382 |
animationTimer = new AnimationTimer(lbl, lbl.getIcon()); |
383 |
} else { |
384 |
animationTimer = null; |
385 |
} |
258 |
searchPanel.setLayout(new BoxLayout(searchPanel, BoxLayout.X_AXIS)); |
386 |
searchPanel.setLayout(new BoxLayout(searchPanel, BoxLayout.X_AXIS)); |
259 |
searchPanel.add(lbl); |
387 |
searchPanel.add(lbl); |
260 |
searchPanel.add(searchTextField); |
388 |
searchPanel.add(searchTextField); |
Lines 263-274
Link Here
|
263 |
searchTextField.setMaximumSize(searchTextField.getPreferredSize()); |
391 |
searchTextField.setMaximumSize(searchTextField.getPreferredSize()); |
264 |
searchTextField.putClientProperty("JTextField.variant", "search"); //NOI18N |
392 |
searchTextField.putClientProperty("JTextField.variant", "search"); //NOI18N |
265 |
lbl.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5)); |
393 |
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) { |
394 |
if (constraints == null) { |
273 |
component.add(searchPanel); |
395 |
component.add(searchPanel); |
274 |
} else { |
396 |
} else { |
Lines 284-298
Link Here
|
284 |
if (searchPanel == null) { |
406 |
if (searchPanel == null) { |
285 |
return; |
407 |
return; |
286 |
} |
408 |
} |
287 |
component.remove(searchPanel); |
409 |
if (animationTimer != null) { |
|
|
410 |
animationTimer.stopProgressAnimation(); |
411 |
} |
412 |
Component sp = searchPanel; |
288 |
searchPanel = null; |
413 |
searchPanel = null; |
289 |
//getViewport().setScrollMode(originalScrollMode); |
414 |
component.remove(sp); |
290 |
component.invalidate(); |
415 |
component.invalidate(); |
291 |
component.revalidate(); |
416 |
component.revalidate(); |
292 |
component.repaint(); |
417 |
component.repaint(); |
293 |
} |
418 |
} |
294 |
|
419 |
|
295 |
public static String findMaxCommonSubstring(String str1, String str2, boolean ignoreCase) { |
420 |
/** Accessed from test. */ |
|
|
421 |
JTextField getSearchField() { |
422 |
return searchTextField; |
423 |
} |
424 |
|
425 |
/** |
426 |
* Utility method, that finds a greatest common prefix of two supplied |
427 |
* strings. |
428 |
* |
429 |
* @param str1 The first string |
430 |
* @param str2 The second string |
431 |
* @param ignoreCase Whether to ignore case in the comparisons |
432 |
* @return The greatest common prefix of the two strings. |
433 |
*/ |
434 |
public static String findMaxPrefix(String str1, String str2, boolean ignoreCase) { |
296 |
int n1 = str1.length(); |
435 |
int n1 = str1.length(); |
297 |
int n2 = str2.length(); |
436 |
int n2 = str2.length(); |
298 |
int i = 0; |
437 |
int i = 0; |
Lines 315-339
Link Here
|
315 |
} |
454 |
} |
316 |
return str1.substring(0, i); |
455 |
return str1.substring(0, i); |
317 |
} |
456 |
} |
|
|
457 |
|
458 |
private final static class AnimationTimer { |
459 |
|
460 |
private final JLabel jLabel; |
461 |
private final Icon findIcon; |
462 |
private final Timer animationTimer; |
463 |
|
464 |
public AnimationTimer(final JLabel jLabel, Icon findIcon) { |
465 |
this.jLabel = jLabel; |
466 |
this.findIcon = findIcon; |
467 |
animationTimer = new Timer(100, new ActionListener() { |
318 |
|
468 |
|
319 |
public static interface QuickSearchListener { |
469 |
ImageIcon icons[]; |
|
|
470 |
int index = 0; |
471 |
|
472 |
@Override |
473 |
public void actionPerformed(ActionEvent e) { |
474 |
if (icons == null) { |
475 |
icons = new ImageIcon[8]; |
476 |
for (int i = 0; i < 8; i++) { |
477 |
icons[i] = ImageUtilities.loadImageIcon("org/openide/awt/resources/quicksearch/progress_" + i + ".png", false); //NOI18N |
478 |
} |
479 |
} |
480 |
jLabel.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1, 1, 6)); |
481 |
jLabel.setIcon(icons[index]); |
482 |
//mac os x |
483 |
jLabel.repaint(); |
484 |
|
485 |
index = (index + 1) % 8; |
486 |
} |
487 |
}); |
488 |
} |
320 |
|
489 |
|
321 |
void quickSearchUpdate(String searchText); |
490 |
public void startProgressAnimation() { |
|
|
491 |
if (animationTimer != null && !animationTimer.isRunning()) { |
492 |
animationTimer.start(); |
493 |
} |
494 |
} |
495 |
|
496 |
public void stopProgressAnimation() { |
497 |
if (animationTimer != null && animationTimer.isRunning()) { |
498 |
animationTimer.stop(); |
499 |
jLabel.setIcon(findIcon); |
500 |
jLabel.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1, 1, 1)); |
501 |
} |
502 |
} |
503 |
|
504 |
} |
505 |
|
506 |
private class LazyFire implements Runnable { |
322 |
|
507 |
|
323 |
void showNextSelection(Bias bias); |
508 |
private final QS_FIRE fire; |
|
|
509 |
private final QuickSearchListener[] qsls; |
510 |
private final String searchText; |
511 |
private final boolean forward; |
512 |
private final DataContentHandlerFactory newPrefixSetter; |
324 |
|
513 |
|
325 |
String findMaxPrefix(String prefix); |
514 |
LazyFire(QS_FIRE fire, QuickSearchListener[] qsls, String searchText) { |
|
|
515 |
this(fire, qsls, searchText, true, null); |
516 |
} |
326 |
|
517 |
|
327 |
void quickSearchConfirmed(); |
518 |
LazyFire(QS_FIRE fire, QuickSearchListener[] qsls, boolean forward) { |
|
|
519 |
this(fire, qsls, null, forward); |
520 |
} |
328 |
|
521 |
|
329 |
void quickSearchCanceled(); |
522 |
LazyFire(QS_FIRE fire, QuickSearchListener[] qsls, String searchText, boolean forward) { |
|
|
523 |
this(fire, qsls, searchText, forward, null); |
524 |
} |
525 |
|
526 |
LazyFire(QS_FIRE fire, QuickSearchListener[] qsls, String searchText, |
527 |
DataContentHandlerFactory newPrefixSetter) { |
528 |
this(fire, qsls, searchText, true, newPrefixSetter); |
529 |
} |
530 |
|
531 |
LazyFire(QS_FIRE fire, QuickSearchListener[] qsls, String searchText, boolean forward, |
532 |
DataContentHandlerFactory newPrefixSetter) { |
533 |
this.fire = fire; |
534 |
this.qsls = qsls; |
535 |
this.searchText = searchText; |
536 |
this.forward = forward; |
537 |
this.newPrefixSetter = newPrefixSetter; |
538 |
animationTimer.startProgressAnimation(); |
539 |
} |
330 |
|
540 |
|
|
|
541 |
@Override |
542 |
public void run() { |
543 |
try { |
544 |
switch (fire) { |
545 |
case UPDATE: fireQuickSearchUpdate(qsls, searchText); |
546 |
break; |
547 |
case NEXT: fireShowNextSelection(qsls, forward); |
548 |
break; |
549 |
case MAX: String mp = findMaxPrefix(qsls, searchText); |
550 |
newPrefixSetter.createDataContentHandler(mp); |
551 |
break; |
552 |
} |
553 |
} finally { |
554 |
animationTimer.stopProgressAnimation(); |
555 |
} |
556 |
} |
331 |
} |
557 |
} |
332 |
|
558 |
|
333 |
private static class SearchPanel extends JPanel { |
559 |
private static class SearchPanel extends JPanel { |
334 |
|
560 |
|
|
|
561 |
public static final boolean isAquaLaF = |
562 |
"Aqua".equals(UIManager.getLookAndFeel().getID()); //NOI18N |
563 |
|
335 |
public SearchPanel() { |
564 |
public SearchPanel() { |
336 |
if (ViewUtil.isAquaLaF) { |
565 |
if (isAquaLaF) { |
337 |
setBorder(BorderFactory.createEmptyBorder(9,6,8,2)); |
566 |
setBorder(BorderFactory.createEmptyBorder(9,6,8,2)); |
338 |
} else { |
567 |
} else { |
339 |
setBorder(BorderFactory.createEmptyBorder(2,6,2,2)); |
568 |
setBorder(BorderFactory.createEmptyBorder(2,6,2,2)); |
Lines 343-351
Link Here
|
343 |
|
572 |
|
344 |
@Override |
573 |
@Override |
345 |
protected void paintComponent(Graphics g) { |
574 |
protected void paintComponent(Graphics g) { |
346 |
if (ViewUtil.isAquaLaF && g instanceof Graphics2D) { |
575 |
if (isAquaLaF && g instanceof Graphics2D) { |
347 |
Graphics2D g2d = (Graphics2D) g; |
576 |
Graphics2D g2d = (Graphics2D) g; |
348 |
g2d.setPaint(new GradientPaint(0, 0, UIManager.getColor("NbExplorerView.quicksearch.background.top"), |
577 |
g2d.setPaint(new GradientPaint(0, 0, UIManager.getColor("NbExplorerView.quicksearch.background.top"), //NOI18N |
349 |
0, getHeight(), UIManager.getColor("NbExplorerView.quicksearch.background.bottom")));//NOI18N |
578 |
0, getHeight(), UIManager.getColor("NbExplorerView.quicksearch.background.bottom")));//NOI18N |
350 |
g2d.fillRect(0, 0, getWidth(), getHeight()); |
579 |
g2d.fillRect(0, 0, getWidth(), getHeight()); |
351 |
g2d.setColor(UIManager.getColor("NbExplorerView.quicksearch.border")); //NOI18N |
580 |
g2d.setColor(UIManager.getColor("NbExplorerView.quicksearch.border")); //NOI18N |
Lines 449-471
Link Here
|
449 |
fireQuickSearchCanceled(); |
678 |
fireQuickSearchCanceled(); |
450 |
e.consume(); |
679 |
e.consume(); |
451 |
} else if (keyCode == KeyEvent.VK_UP || (keyCode == KeyEvent.VK_F3 && e.isShiftDown())) { |
680 |
} else if (keyCode == KeyEvent.VK_UP || (keyCode == KeyEvent.VK_F3 && e.isShiftDown())) { |
452 |
fireShowNextSelection(Bias.Backward); |
681 |
fireShowNextSelection(false); |
453 |
// Stop processing the event here. Otherwise it's dispatched |
682 |
// Stop processing the event here. Otherwise it's dispatched |
454 |
// to the tree too (which scrolls) |
683 |
// to the tree too (which scrolls) |
455 |
e.consume(); |
684 |
e.consume(); |
456 |
} else if (keyCode == KeyEvent.VK_DOWN || keyCode == KeyEvent.VK_F3) { |
685 |
} else if (keyCode == KeyEvent.VK_DOWN || keyCode == KeyEvent.VK_F3) { |
457 |
fireShowNextSelection(Bias.Forward); |
686 |
fireShowNextSelection(true); |
458 |
// Stop processing the event here. Otherwise it's dispatched |
687 |
// Stop processing the event here. Otherwise it's dispatched |
459 |
// to the tree too (which scrolls) |
688 |
// to the tree too (which scrolls) |
460 |
e.consume(); |
689 |
e.consume(); |
461 |
} else if (keyCode == KeyEvent.VK_TAB) { |
690 |
} else if (keyCode == KeyEvent.VK_TAB) { |
462 |
String maxPrefix = findMaxPrefix(searchTextField.getText()); |
691 |
findMaxPrefix(searchTextField.getText(), new DataContentHandlerFactory() { |
463 |
ignoreEvents = true; |
692 |
@Override |
464 |
try { |
693 |
public DataContentHandler createDataContentHandler(final String maxPrefix) { |
465 |
searchTextField.setText(maxPrefix); |
694 |
if (!SwingUtilities.isEventDispatchThread()) { |
466 |
} finally { |
695 |
SwingUtilities.invokeLater(new Runnable() { |
467 |
ignoreEvents = false; |
696 |
@Override |
468 |
} |
697 |
public void run() { |
|
|
698 |
createDataContentHandler(maxPrefix); |
699 |
} |
700 |
}); |
701 |
return null; |
702 |
} |
703 |
ignoreEvents = true; |
704 |
try { |
705 |
searchTextField.setText(maxPrefix); |
706 |
} finally { |
707 |
ignoreEvents = false; |
708 |
} |
709 |
return null; |
710 |
} |
711 |
}); |
469 |
|
712 |
|
470 |
e.consume(); |
713 |
e.consume(); |
471 |
} else if (keyCode == KeyEvent.VK_ENTER) { |
714 |
} else if (keyCode == KeyEvent.VK_ENTER) { |
Lines 506-513
Link Here
|
506 |
if (oppositeComponent == searchTextField) { |
749 |
if (oppositeComponent == searchTextField) { |
507 |
return ; |
750 |
return ; |
508 |
} |
751 |
} |
509 |
removeSearchField(); |
752 |
if (searchPanel != null) { |
510 |
fireQuickSearchConfirmed(); |
753 |
removeSearchField(); |
|
|
754 |
fireQuickSearchConfirmed(); |
755 |
} |
511 |
} |
756 |
} |
512 |
} |
757 |
} |
513 |
|
758 |
|