Index: editor/lib2/src/org/netbeans/api/editor/EditorRegistry.java
===================================================================
RCS file: /cvs/editor/lib2/src/org/netbeans/api/editor/EditorRegistry.java,v
--- editor/lib2/src/org/netbeans/api/editor/EditorRegistry.java 1 Oct 2007 15:46:37 -0000 1.3
+++ editor/lib2/src/org/netbeans/api/editor/EditorRegistry.java 21 Oct 2007 22:39:55 -0000
@@ -42,6 +42,8 @@
package org.netbeans.api.editor;
import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.beans.PropertyChangeEvent;
@@ -53,6 +55,10 @@
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
+import javax.swing.JComponent;
+import javax.swing.Timer;
+import javax.swing.event.AncestorEvent;
+import javax.swing.event.AncestorListener;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import org.netbeans.lib.editor.util.ArrayUtilities;
@@ -89,7 +95,7 @@
*
* The focused component will become the first in the components list.
*
- * The {@link java.beans.PropertyChangeEvent#getOldValue()} will be the a component
+ * The {@link java.beans.PropertyChangeEvent#getOldValue()} will be a component
* losing the focus {@link FocusEvent#getOppositeComponent()}.
* The {@link java.beans.PropertyChangeEvent#getNewValue()} will be the text component gaining the focus.
*/
@@ -117,6 +123,25 @@
public static final String FOCUSED_DOCUMENT_PROPERTY = "focusedDocument";
/**
+ * Fired when the last focused component (returned previously from {@link #lastFocusedComponent()})
+ * was removed from component hierarchy (so it's likely that the component will be released completely
+ * and garbage-collected).
+ *
+ * Such component will no longer be returned from {@link #componentList()}
+ * or {@link #lastFocusedComponent()}.
+ *
+ * The {@link java.beans.PropertyChangeEvent#getOldValue()} will be the removed
+ * last focused component and the {@link java.beans.PropertyChangeEvent#getNewValue()}
+ * will be the component that would currently be returned from {@link #lastFocusedComponent()}.
+ *
+ * If {@link java.beans.PropertyChangeEvent#getNewValue()} returns null
+ * then there are no longer any registered components
+ * ({@link #componentList()} would return empty list). If the client
+ * holds per-last-focused-component data it should clear them.
+ */
+ public static final String LAST_FOCUSED_REMOVED_PROPERTY = "lastFocusedRemoved";
+
+ /**
* Double linked list of weak references to text components.
*/
private static Item textComponentRefs;
@@ -213,16 +238,9 @@
Item item = new Item(c);
c.putClientProperty(Item.class, item);
c.addFocusListener(FocusL.INSTANCE);
+ c.addAncestorListener(AncestorL.INSTANCE);
// Add to end of list
- if (textComponentRefs == null)
- textComponentRefs = item;
- else {
- Item i = textComponentRefs;
- while (i.next != null)
- i = i.next;
- i.next = item;
- item.previous = i;
- }
+ addAsLast(item);
if (LOG.isLoggable(Level.FINE)) {
LOG.log(Level.FINE, "REGISTERED new component as last item:\n" + dumpItemList());
}
@@ -271,10 +289,39 @@
return c;
}
+ private static void addAsLast(Item item) {
+ if (item.linked)
+ return;
+ item.linked = true;
+ if (textComponentRefs == null) {
+ textComponentRefs = item;
+ } else {
+ Item i = textComponentRefs;
+ while (i.next != null)
+ i = i.next;
+ i.next = item;
+ item.previous = i;
+ }
+ // Assuming item.next == null (done in removeItem() too).
+ }
+
+ private static void addAsFirst(Item item) {
+ if (item.linked)
+ return;
+ item.linked = true;
+ item.next = textComponentRefs;
+ if (textComponentRefs != null)
+ textComponentRefs.previous = item;
+ textComponentRefs = item;
+ }
+
/**
* Remove given entry and return a next one.
*/
private static Item removeItem(Item item) {
+ if (!item.linked)
+ return null;
+ item.linked = false;
Item next = item.next;
if (item.previous == null) { // Head
assert (textComponentRefs == item);
@@ -288,25 +335,6 @@
return next;
}
- private static void moveToHead(Item item) {
- if (LOG.isLoggable(Level.FINEST)) { // Debugging
- isItemInList(item);
- }
- removeItem(item);
- item.next = textComponentRefs;
- if (textComponentRefs != null)
- textComponentRefs.previous = item;
- textComponentRefs = item;
- if (LOG.isLoggable(Level.FINEST)) { // Debugging
- isItemInList(item);
- checkItemListConsistency();
- }
- }
-
- private static void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
- pcs.firePropertyChange(propertyName, oldValue, newValue);
- }
-
private static boolean isItemInList(Item item) {
Item i = textComponentRefs;
while (i != null) {
@@ -321,14 +349,30 @@
Item item = textComponentRefs;
Item previous = null;
while (item != null) {
- assert item.previous == previous;
+ assert item.linked : "item=" + item + " is in list but not linked.";
+ assert item.previous == previous : "Invalid previous of item=" + item;
previous = item;
item = item.next;
}
- if (previous != null)
- assert previous.next == null;
}
+ private static void moveToHead(Item item) {
+ if (LOG.isLoggable(Level.FINEST)) { // Debugging
+ isItemInList(item);
+ checkItemListConsistency();
+ }
+ removeItem(item);
+ addAsFirst(item);
+ if (LOG.isLoggable(Level.FINEST)) { // Debugging
+ isItemInList(item);
+ checkItemListConsistency();
+ }
+ }
+
+ private static void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
+ pcs.firePropertyChange(propertyName, oldValue, newValue);
+ }
+
private static String dumpItemList() {
StringBuilder sb = new StringBuilder();
int i = 0;
@@ -344,7 +388,7 @@
return sb.toString();
}
- private static String dumpComponent(JTextComponent c) {
+ static String dumpComponent(JComponent c) {
return "c[IHC=" + System.identityHashCode(c)
+ "]=" + c;
}
@@ -358,9 +402,20 @@
super(c);
}
+ boolean linked;
+
Item next;
Item previous;
+
+ Timer runningTimer;
+
+ @Override
+ public String toString() {
+ return "component=" + get() + ", linked=" + linked +
+ ", hasTimer=" + (runningTimer != null) +
+ ", hasPrevious=" + (previous != null) + ", hasNext=" + (next != null);
+ }
}
@@ -385,10 +440,62 @@
public void propertyChange(PropertyChangeEvent evt) {
if ("document".equals(evt.getPropertyName())) {
- focusedDocumentChange((JTextComponent)evt.getSource(), (Document)evt.getOldValue(), (Document)evt.getNewValue());
+ focusedDocumentChange((JTextComponent)evt.getSource(),
+ (Document)evt.getOldValue(), (Document)evt.getNewValue());
+ }
+ }
+
+ }
+
+ private static final class AncestorL implements AncestorListener {
+
+ static final AncestorL INSTANCE = new AncestorL();
+
+ private static final int BEFORE_REMOVE_DELAY = 2000; // 2000ms delay
+
+ public void ancestorAdded(AncestorEvent event) {
+ Item item = (Item)event.getComponent().getClientProperty(Item.class);
+ if (item.runningTimer != null) {
+ item.runningTimer.stop();
+ item.runningTimer = null;
}
+ // If the component was removed from the component hierarchy and then
+ // returned back to the hierarchy it will be readded to the component list.
+ // If the item is not removed yet then the addToEnd() will do nothing.
+ addAsLast(item);
+ if (LOG.isLoggable(Level.FINER)) {
+ LOG.fine("ancestorAdded: c=" + dumpComponent(event.getComponent()) + '\n');
+ }
+ }
+
+ public void ancestorMoved(AncestorEvent event) {
}
+ public void ancestorRemoved(AncestorEvent event) {
+ final JComponent component = event.getComponent();
+ Item item = (Item)component.getClientProperty(Item.class);
+ if (LOG.isLoggable(Level.FINER)) {
+ LOG.fine("ancestorRemoved: c=" + dumpComponent(event.getComponent()) + '\n');
+ }
+ item.runningTimer = new Timer(BEFORE_REMOVE_DELAY,
+ new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ Item item = (Item)component.getClientProperty(Item.class);
+ item.runningTimer.stop();
+ item.runningTimer = null;
+ // Remove component from item chain
+ removeItem(item);
+ firePropertyChange(LAST_FOCUSED_REMOVED_PROPERTY, component, lastFocusedComponent());
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Timer fired => component removed: c="
+ + dumpComponent(component) + '\n');
+ }
+ }
+ }
+ );
+ item.runningTimer.start();
+ }
+
}
private static final class PackageAccessor extends EditorApiPackageAccessor {