Lines 42-47
Link Here
|
42 |
package org.netbeans.api.editor; |
42 |
package org.netbeans.api.editor; |
43 |
|
43 |
|
44 |
import java.awt.Component; |
44 |
import java.awt.Component; |
|
|
45 |
import java.awt.event.ActionEvent; |
46 |
import java.awt.event.ActionListener; |
45 |
import java.awt.event.FocusEvent; |
47 |
import java.awt.event.FocusEvent; |
46 |
import java.awt.event.FocusListener; |
48 |
import java.awt.event.FocusListener; |
47 |
import java.beans.PropertyChangeEvent; |
49 |
import java.beans.PropertyChangeEvent; |
Lines 53-58
Link Here
|
53 |
import java.util.List; |
55 |
import java.util.List; |
54 |
import java.util.logging.Level; |
56 |
import java.util.logging.Level; |
55 |
import java.util.logging.Logger; |
57 |
import java.util.logging.Logger; |
|
|
58 |
import javax.swing.JComponent; |
59 |
import javax.swing.Timer; |
60 |
import javax.swing.event.AncestorEvent; |
61 |
import javax.swing.event.AncestorListener; |
56 |
import javax.swing.text.Document; |
62 |
import javax.swing.text.Document; |
57 |
import javax.swing.text.JTextComponent; |
63 |
import javax.swing.text.JTextComponent; |
58 |
import org.netbeans.lib.editor.util.ArrayUtilities; |
64 |
import org.netbeans.lib.editor.util.ArrayUtilities; |
Lines 89-95
Link Here
|
89 |
* <br/> |
95 |
* <br/> |
90 |
* The focused component will become the first in the components list. |
96 |
* The focused component will become the first in the components list. |
91 |
* <br/> |
97 |
* <br/> |
92 |
* The {@link java.beans.PropertyChangeEvent#getOldValue()} will be the a component |
98 |
* The {@link java.beans.PropertyChangeEvent#getOldValue()} will be a component |
93 |
* losing the focus {@link FocusEvent#getOppositeComponent()}. |
99 |
* losing the focus {@link FocusEvent#getOppositeComponent()}. |
94 |
* The {@link java.beans.PropertyChangeEvent#getNewValue()} will be the text component gaining the focus. |
100 |
* The {@link java.beans.PropertyChangeEvent#getNewValue()} will be the text component gaining the focus. |
95 |
*/ |
101 |
*/ |
Lines 117-122
Link Here
|
117 |
public static final String FOCUSED_DOCUMENT_PROPERTY = "focusedDocument"; |
123 |
public static final String FOCUSED_DOCUMENT_PROPERTY = "focusedDocument"; |
118 |
|
124 |
|
119 |
/** |
125 |
/** |
|
|
126 |
* Fired when the last focused component (returned previously from {@link #lastFocusedComponent()}) |
127 |
* was removed from component hierarchy (so it's likely that the component will be released completely |
128 |
* and garbage-collected). |
129 |
* <br/> |
130 |
* Such component will no longer be returned from {@link #componentList()} |
131 |
* or {@link #lastFocusedComponent()}. |
132 |
* <br/> |
133 |
* The {@link java.beans.PropertyChangeEvent#getOldValue()} will be the removed |
134 |
* last focused component and the {@link java.beans.PropertyChangeEvent#getNewValue()} |
135 |
* will be the component that would currently be returned from {@link #lastFocusedComponent()}. |
136 |
* <br/> |
137 |
* If {@link java.beans.PropertyChangeEvent#getNewValue()} returns <code>null</code> |
138 |
* then there are no longer any registered components |
139 |
* ({@link #componentList()} would return empty list). If the client |
140 |
* holds per-last-focused-component data it should clear them. |
141 |
*/ |
142 |
public static final String LAST_FOCUSED_REMOVED_PROPERTY = "lastFocusedRemoved"; |
143 |
|
144 |
/** |
120 |
* Double linked list of weak references to text components. |
145 |
* Double linked list of weak references to text components. |
121 |
*/ |
146 |
*/ |
122 |
private static Item textComponentRefs; |
147 |
private static Item textComponentRefs; |
Lines 213-228
Link Here
|
213 |
Item item = new Item(c); |
238 |
Item item = new Item(c); |
214 |
c.putClientProperty(Item.class, item); |
239 |
c.putClientProperty(Item.class, item); |
215 |
c.addFocusListener(FocusL.INSTANCE); |
240 |
c.addFocusListener(FocusL.INSTANCE); |
|
|
241 |
c.addAncestorListener(AncestorL.INSTANCE); |
216 |
// Add to end of list |
242 |
// Add to end of list |
217 |
if (textComponentRefs == null) |
243 |
addAsLast(item); |
218 |
textComponentRefs = item; |
|
|
219 |
else { |
220 |
Item i = textComponentRefs; |
221 |
while (i.next != null) |
222 |
i = i.next; |
223 |
i.next = item; |
224 |
item.previous = i; |
225 |
} |
226 |
if (LOG.isLoggable(Level.FINE)) { |
244 |
if (LOG.isLoggable(Level.FINE)) { |
227 |
LOG.log(Level.FINE, "REGISTERED new component as last item:\n" + dumpItemList()); |
245 |
LOG.log(Level.FINE, "REGISTERED new component as last item:\n" + dumpItemList()); |
228 |
} |
246 |
} |
Lines 271-280
Link Here
|
271 |
return c; |
289 |
return c; |
272 |
} |
290 |
} |
273 |
|
291 |
|
|
|
292 |
private static void addAsLast(Item item) { |
293 |
if (item.linked) |
294 |
return; |
295 |
item.linked = true; |
296 |
if (textComponentRefs == null) { |
297 |
textComponentRefs = item; |
298 |
} else { |
299 |
Item i = textComponentRefs; |
300 |
while (i.next != null) |
301 |
i = i.next; |
302 |
i.next = item; |
303 |
item.previous = i; |
304 |
} |
305 |
// Assuming item.next == null (done in removeItem() too). |
306 |
} |
307 |
|
308 |
private static void addAsFirst(Item item) { |
309 |
if (item.linked) |
310 |
return; |
311 |
item.linked = true; |
312 |
item.next = textComponentRefs; |
313 |
if (textComponentRefs != null) |
314 |
textComponentRefs.previous = item; |
315 |
textComponentRefs = item; |
316 |
} |
317 |
|
274 |
/** |
318 |
/** |
275 |
* Remove given entry and return a next one. |
319 |
* Remove given entry and return a next one. |
276 |
*/ |
320 |
*/ |
277 |
private static Item removeItem(Item item) { |
321 |
private static Item removeItem(Item item) { |
|
|
322 |
if (!item.linked) |
323 |
return null; |
324 |
item.linked = false; |
278 |
Item next = item.next; |
325 |
Item next = item.next; |
279 |
if (item.previous == null) { // Head |
326 |
if (item.previous == null) { // Head |
280 |
assert (textComponentRefs == item); |
327 |
assert (textComponentRefs == item); |
Lines 288-312
Link Here
|
288 |
return next; |
335 |
return next; |
289 |
} |
336 |
} |
290 |
|
337 |
|
291 |
private static void moveToHead(Item item) { |
|
|
292 |
if (LOG.isLoggable(Level.FINEST)) { // Debugging |
293 |
isItemInList(item); |
294 |
} |
295 |
removeItem(item); |
296 |
item.next = textComponentRefs; |
297 |
if (textComponentRefs != null) |
298 |
textComponentRefs.previous = item; |
299 |
textComponentRefs = item; |
300 |
if (LOG.isLoggable(Level.FINEST)) { // Debugging |
301 |
isItemInList(item); |
302 |
checkItemListConsistency(); |
303 |
} |
304 |
} |
305 |
|
306 |
private static void firePropertyChange(String propertyName, Object oldValue, Object newValue) { |
307 |
pcs.firePropertyChange(propertyName, oldValue, newValue); |
308 |
} |
309 |
|
310 |
private static boolean isItemInList(Item item) { |
338 |
private static boolean isItemInList(Item item) { |
311 |
Item i = textComponentRefs; |
339 |
Item i = textComponentRefs; |
312 |
while (i != null) { |
340 |
while (i != null) { |
Lines 321-334
Link Here
|
321 |
Item item = textComponentRefs; |
349 |
Item item = textComponentRefs; |
322 |
Item previous = null; |
350 |
Item previous = null; |
323 |
while (item != null) { |
351 |
while (item != null) { |
324 |
assert item.previous == previous; |
352 |
assert item.linked : "item=" + item + " is in list but not linked."; |
|
|
353 |
assert item.previous == previous : "Invalid previous of item=" + item; |
325 |
previous = item; |
354 |
previous = item; |
326 |
item = item.next; |
355 |
item = item.next; |
327 |
} |
356 |
} |
328 |
if (previous != null) |
|
|
329 |
assert previous.next == null; |
330 |
} |
357 |
} |
331 |
|
358 |
|
|
|
359 |
private static void moveToHead(Item item) { |
360 |
if (LOG.isLoggable(Level.FINEST)) { // Debugging |
361 |
isItemInList(item); |
362 |
checkItemListConsistency(); |
363 |
} |
364 |
removeItem(item); |
365 |
addAsFirst(item); |
366 |
if (LOG.isLoggable(Level.FINEST)) { // Debugging |
367 |
isItemInList(item); |
368 |
checkItemListConsistency(); |
369 |
} |
370 |
} |
371 |
|
372 |
private static void firePropertyChange(String propertyName, Object oldValue, Object newValue) { |
373 |
pcs.firePropertyChange(propertyName, oldValue, newValue); |
374 |
} |
375 |
|
332 |
private static String dumpItemList() { |
376 |
private static String dumpItemList() { |
333 |
StringBuilder sb = new StringBuilder(); |
377 |
StringBuilder sb = new StringBuilder(); |
334 |
int i = 0; |
378 |
int i = 0; |
Lines 344-350
Link Here
|
344 |
return sb.toString(); |
388 |
return sb.toString(); |
345 |
} |
389 |
} |
346 |
|
390 |
|
347 |
private static String dumpComponent(JTextComponent c) { |
391 |
static String dumpComponent(JComponent c) { |
348 |
return "c[IHC=" + System.identityHashCode(c) |
392 |
return "c[IHC=" + System.identityHashCode(c) |
349 |
+ "]=" + c; |
393 |
+ "]=" + c; |
350 |
} |
394 |
} |
Lines 358-366
Link Here
|
358 |
super(c); |
402 |
super(c); |
359 |
} |
403 |
} |
360 |
|
404 |
|
|
|
405 |
boolean linked; |
406 |
|
361 |
Item next; |
407 |
Item next; |
362 |
|
408 |
|
363 |
Item previous; |
409 |
Item previous; |
|
|
410 |
|
411 |
Timer runningTimer; |
412 |
|
413 |
@Override |
414 |
public String toString() { |
415 |
return "component=" + get() + ", linked=" + linked + |
416 |
", hasTimer=" + (runningTimer != null) + |
417 |
", hasPrevious=" + (previous != null) + ", hasNext=" + (next != null); |
418 |
} |
364 |
|
419 |
|
365 |
} |
420 |
} |
366 |
|
421 |
|
Lines 385-394
Link Here
|
385 |
|
440 |
|
386 |
public void propertyChange(PropertyChangeEvent evt) { |
441 |
public void propertyChange(PropertyChangeEvent evt) { |
387 |
if ("document".equals(evt.getPropertyName())) { |
442 |
if ("document".equals(evt.getPropertyName())) { |
388 |
focusedDocumentChange((JTextComponent)evt.getSource(), (Document)evt.getOldValue(), (Document)evt.getNewValue()); |
443 |
focusedDocumentChange((JTextComponent)evt.getSource(), |
|
|
444 |
(Document)evt.getOldValue(), (Document)evt.getNewValue()); |
445 |
} |
446 |
} |
447 |
|
448 |
} |
449 |
|
450 |
private static final class AncestorL implements AncestorListener { |
451 |
|
452 |
static final AncestorL INSTANCE = new AncestorL(); |
453 |
|
454 |
private static final int BEFORE_REMOVE_DELAY = 2000; // 2000ms delay |
455 |
|
456 |
public void ancestorAdded(AncestorEvent event) { |
457 |
Item item = (Item)event.getComponent().getClientProperty(Item.class); |
458 |
if (item.runningTimer != null) { |
459 |
item.runningTimer.stop(); |
460 |
item.runningTimer = null; |
389 |
} |
461 |
} |
|
|
462 |
// If the component was removed from the component hierarchy and then |
463 |
// returned back to the hierarchy it will be readded to the component list. |
464 |
// If the item is not removed yet then the addToEnd() will do nothing. |
465 |
addAsLast(item); |
466 |
if (LOG.isLoggable(Level.FINER)) { |
467 |
LOG.fine("ancestorAdded: c=" + dumpComponent(event.getComponent()) + '\n'); |
468 |
} |
469 |
} |
470 |
|
471 |
public void ancestorMoved(AncestorEvent event) { |
390 |
} |
472 |
} |
391 |
|
473 |
|
|
|
474 |
public void ancestorRemoved(AncestorEvent event) { |
475 |
final JComponent component = event.getComponent(); |
476 |
Item item = (Item)component.getClientProperty(Item.class); |
477 |
if (LOG.isLoggable(Level.FINER)) { |
478 |
LOG.fine("ancestorRemoved: c=" + dumpComponent(event.getComponent()) + '\n'); |
479 |
} |
480 |
item.runningTimer = new Timer(BEFORE_REMOVE_DELAY, |
481 |
new ActionListener() { |
482 |
public void actionPerformed(ActionEvent e) { |
483 |
Item item = (Item)component.getClientProperty(Item.class); |
484 |
item.runningTimer.stop(); |
485 |
item.runningTimer = null; |
486 |
// Remove component from item chain |
487 |
removeItem(item); |
488 |
firePropertyChange(LAST_FOCUSED_REMOVED_PROPERTY, component, lastFocusedComponent()); |
489 |
if (LOG.isLoggable(Level.FINE)) { |
490 |
LOG.fine("Timer fired => component removed: c=" |
491 |
+ dumpComponent(component) + '\n'); |
492 |
} |
493 |
} |
494 |
} |
495 |
); |
496 |
item.runningTimer.start(); |
497 |
} |
498 |
|
392 |
} |
499 |
} |
393 |
|
500 |
|
394 |
private static final class PackageAccessor extends EditorApiPackageAccessor { |
501 |
private static final class PackageAccessor extends EditorApiPackageAccessor { |