# This patch file was generated by NetBeans IDE # This patch can be applied using context Tools: Apply Diff Patch action on respective folder. # It uses platform neutral UTF-8 encoding. # Above lines and this line are ignored by the patching process. Index: editor/lib2/src/org/netbeans/api/editor/EditorRegistry.java --- editor/lib2/src/org/netbeans/api/editor/EditorRegistry.java Base (1.3) +++ editor/lib2/src/org/netbeans/api/editor/EditorRegistry.java Locally Modified (Based On 1.3) @@ -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()); } @@ -237,7 +255,7 @@ static synchronized void focusGained(JTextComponent c, Component origFocused) { Item item = (Item)c.getClientProperty(Item.class); assert (item != null) : "Not registered!"; // NOI18N - assert (item.next != null || item.previous != null || textComponentRefs == item) + assert (!item.linked || item.next != null || item.previous != null || textComponentRefs == item) : "Already released!"; // NOI18N moveToHead(item); c.addPropertyChangeListener(PropertyDocL.INSTANCE); @@ -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,12 +402,23 @@ 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); } + } + private static final class FocusL implements FocusListener { static final FocusL INSTANCE = new FocusL(); @@ -385,12 +440,64 @@ 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 { public void register(JTextComponent c) {