Added
Link Here
|
1 |
/* |
2 |
* The contents of this file are subject to the terms of the Common Development |
3 |
* and Distribution License (the License). You may not use this file except in |
4 |
* compliance with the License. |
5 |
* |
6 |
* You can obtain a copy of the License at http://www.netbeans.org/cddl.html |
7 |
* or http://www.netbeans.org/cddl.txt. |
8 |
* |
9 |
* When distributing Covered Code, include this CDDL Header Notice in each file |
10 |
* and include the License file at http://www.netbeans.org/cddl.txt. |
11 |
* If applicable, add the following below the CDDL Header, with the fields |
12 |
* enclosed by brackets [] replaced by your own identifying information: |
13 |
* "Portions Copyrighted [year] [name of copyright owner]" |
14 |
* |
15 |
* The Original Software is NetBeans. The Initial Developer of the Original |
16 |
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun |
17 |
* Microsystems, Inc. All Rights Reserved. |
18 |
*/ |
19 |
|
20 |
package org.netbeans.api.editor; |
21 |
|
22 |
import java.awt.Component; |
23 |
import java.awt.event.FocusEvent; |
24 |
import java.awt.event.FocusListener; |
25 |
import java.beans.PropertyChangeEvent; |
26 |
import java.beans.PropertyChangeListener; |
27 |
import java.beans.PropertyChangeSupport; |
28 |
import java.lang.ref.WeakReference; |
29 |
import java.util.ArrayList; |
30 |
import java.util.Collections; |
31 |
import java.util.List; |
32 |
import java.util.logging.Level; |
33 |
import java.util.logging.Logger; |
34 |
import javax.swing.text.Document; |
35 |
import javax.swing.text.JTextComponent; |
36 |
import org.netbeans.lib.editor.util.ArrayUtilities; |
37 |
|
38 |
/** |
39 |
* Registry maintaining {@link JTextComponent}s in most-recently-used order. |
40 |
* <br/> |
41 |
* The particular text component needs to register itself first (to avoid dealing |
42 |
* with all the JTextFields etc.). Then the registry will attach |
43 |
* a focus listener to the text component and once the component gains |
44 |
* the focus it will move to the head of the components list. |
45 |
* <br/> |
46 |
* The registry will also fire a change in case a document property |
47 |
* of the focused component changes (by calling component.setDocument()). |
48 |
* |
49 |
* @author Miloslav Metelka |
50 |
*/ |
51 |
public class EditorRegistry { |
52 |
|
53 |
// -J-Dorg.netbeans.api.editor.EditorRegistry.level=FINEST |
54 |
private static final Logger LOG = Logger.getLogger(EditorRegistry.class.getName()); |
55 |
|
56 |
/** |
57 |
* Fired when focus was delivered to a registered text component. |
58 |
* <br/> |
59 |
* The focused component will become the first in the components list. |
60 |
* <br/> |
61 |
* The {@link PropertyEvent#getOldValue()} will be the a component |
62 |
* losing the focus {@link FocusEvent#getOppositeComponent()}. |
63 |
* The {@link PropertyEvent#getNewValue()} will be the text component gaining the focus. |
64 |
*/ |
65 |
public static final String FOCUS_GAINED_PROPERTY = "focusGained"; |
66 |
|
67 |
/** |
68 |
* Fired when a registered focused component has lost the focus. |
69 |
* <br/> |
70 |
* The focused component will remain the first in the components list. |
71 |
* <br/> |
72 |
* The {@link PropertyEvent#getOldValue()} will be the text component |
73 |
* losing the focus and the {@link PropertyEvent#getNewValue()} |
74 |
* will be the component gaining the focus {@link FocusEvent#getOppositeComponent()}. |
75 |
*/ |
76 |
public static final String FOCUS_LOST_PROPERTY = "focusLost"; |
77 |
|
78 |
/** |
79 |
* Fired when document property of the focused component changes |
80 |
* i.e. someone has called {@link JTextComponent#setDocument(Document)}. |
81 |
* <br/> |
82 |
* The {@link PropertyEvent#getOldValue()} will be the original document |
83 |
* of the focused text component and the {@link PropertyEvent#getNewValue()} |
84 |
* will be the new document set to the focused text component. |
85 |
*/ |
86 |
public static final String FOCUSED_DOCUMENT_PROPERTY = "focusedDocument"; |
87 |
|
88 |
/** |
89 |
* Double linked list of weak references to text components. |
90 |
*/ |
91 |
private static Item textComponentRefs; |
92 |
|
93 |
private static final PropertyChangeSupport pcs = new PropertyChangeSupport(EditorRegistry.class); |
94 |
|
95 |
|
96 |
/** |
97 |
* Return last focused text component (from the ones included in the registry). |
98 |
* <br/> |
99 |
* It may or may not currently have a focus. |
100 |
* |
101 |
* @return last focused text component or null if no text components |
102 |
* were registered yet. |
103 |
*/ |
104 |
public static synchronized JTextComponent lastFocusedComponent() { |
105 |
return firstValidComponent(); |
106 |
} |
107 |
|
108 |
/** |
109 |
* Return the last focused component if it currently has a focus |
110 |
* or return null if none of the registered components currently have the focus. |
111 |
* <br/> |
112 |
* @return focused component or null if none of the registered components |
113 |
* is currently focused. |
114 |
*/ |
115 |
public static synchronized JTextComponent focusedComponent() { |
116 |
JTextComponent c = firstValidComponent(); |
117 |
return (c != null && c.isFocusOwner()) ? c : null; |
118 |
} |
119 |
|
120 |
/** |
121 |
* Get list of all components present in the registry starting with the most active |
122 |
* and ending with least active component. |
123 |
* <br/> |
124 |
* The list is a snapshot of the current state and it may be modified |
125 |
* by the caller if desired. |
126 |
* |
127 |
* @return non-null list containing all the registered components in MRU order. |
128 |
*/ |
129 |
public static synchronized List<JTextComponent> componentList() { |
130 |
List<JTextComponent> l; |
131 |
JTextComponent c = firstValidComponent(); |
132 |
if (c != null) { |
133 |
l = new ArrayList<JTextComponent>(); |
134 |
l.add(c); |
135 |
// Add remaining ones (eliminate empty items) |
136 |
Item item = textComponentRefs.next; |
137 |
while (item != null) { |
138 |
c = item.get(); |
139 |
if (c != null) { |
140 |
l.add(c); |
141 |
item = item.next; |
142 |
} else |
143 |
item = removeItem(item); |
144 |
} |
145 |
|
146 |
} else // No valid items |
147 |
l = Collections.emptyList(); |
148 |
return l; |
149 |
} |
150 |
|
151 |
/** |
152 |
* Add a given text component to the registry. The registry will weakly |
153 |
* reference the given component for its whole lifetime |
154 |
* until it will be garbage collected. |
155 |
* |
156 |
* @param c non-null text component to be registered. |
157 |
*/ |
158 |
public static synchronized void register(JTextComponent c) { |
159 |
assert (c != null); |
160 |
if (c.getClientProperty(Item.class) == null) { // Not registered yet |
161 |
Item item = new Item(c); |
162 |
c.putClientProperty(Item.class, item); |
163 |
c.addFocusListener(FocusL.INSTANCE); |
164 |
// Add to end of list |
165 |
if (textComponentRefs == null) |
166 |
textComponentRefs = item; |
167 |
else { |
168 |
Item i = textComponentRefs; |
169 |
while (i.next != null) |
170 |
i = i.next; |
171 |
i.next = item; |
172 |
item.previous = i; |
173 |
} |
174 |
if (LOG.isLoggable(Level.FINE)) { |
175 |
LOG.log(Level.FINE, "REGISTERED new component as last item:\n" + dumpItemList()); |
176 |
} |
177 |
|
178 |
// If focused (rare since usually registered early when not focused yet) |
179 |
if (c.isFocusOwner()) { |
180 |
focusGained(c, null); // opposite could eventually be got from Focus Manager |
181 |
} |
182 |
} |
183 |
} |
184 |
|
185 |
/** |
186 |
* Add a property change listener for either of the following properties: |
187 |
* <ul> |
188 |
* <li>{@link #FOCUS_GAINED_PROPERTY}</li> |
189 |
* <li>{@link #FOCUS_LOST_PROPERTY}</li> |
190 |
* <li>{@link #FOCUSED_DOCUMENT_PROPERTY}</li> |
191 |
* </ul>. |
192 |
* <br/> |
193 |
* All the firing should occur in AWT thread only |
194 |
* (assuming the JTextComponent.setDocument() is done properly in AWT). |
195 |
* |
196 |
* @param l non-null listener to add. |
197 |
*/ |
198 |
public static void addPropertyChangeListener(PropertyChangeListener l) { |
199 |
pcs.addPropertyChangeListener(l); |
200 |
} |
201 |
|
202 |
public static void removePropertyChangeListener(PropertyChangeListener l) { |
203 |
pcs.removePropertyChangeListener(l); |
204 |
} |
205 |
|
206 |
static synchronized void focusGained(JTextComponent c, Component origFocused) { |
207 |
Item item = (Item)c.getClientProperty(Item.class); |
208 |
assert (item != null) : "Not registered!"; // NOI18N |
209 |
assert (item.next != null || item.previous != null || textComponentRefs == item) |
210 |
: "Already released!"; // NOI18N |
211 |
moveToHead(item); |
212 |
c.addPropertyChangeListener(PropertyDocL.INSTANCE); |
213 |
if (LOG.isLoggable(Level.FINE)) { |
214 |
LOG.log(Level.FINE, FOCUS_GAINED_PROPERTY + ": " + dumpComponent(c) + '\n'); |
215 |
} |
216 |
firePropertyChange(FOCUS_GAINED_PROPERTY, origFocused, c); |
217 |
} |
218 |
|
219 |
static void focusLost(JTextComponent c, Component newFocused) { |
220 |
c.removePropertyChangeListener(PropertyDocL.INSTANCE); |
221 |
if (LOG.isLoggable(Level.FINE)) { |
222 |
LOG.log(Level.FINE, FOCUS_LOST_PROPERTY + ": " + dumpComponent(c) + '\n'); |
223 |
} |
224 |
firePropertyChange(FOCUS_LOST_PROPERTY, c, newFocused); |
225 |
} |
226 |
|
227 |
static void focusedDocumentChange(JTextComponent c, Document oldDoc, Document newDoc) { |
228 |
if (LOG.isLoggable(Level.FINE)) { |
229 |
LOG.log(Level.FINE, FOCUSED_DOCUMENT_PROPERTY + ": " + dumpComponent(c) |
230 |
+ "\n OLDDoc=" + oldDoc + "\n NEWDoc=" + newDoc + '\n'); |
231 |
} |
232 |
firePropertyChange(FOCUSED_DOCUMENT_PROPERTY, oldDoc, newDoc); |
233 |
} |
234 |
|
235 |
private static JTextComponent firstValidComponent() { |
236 |
JTextComponent c = null; |
237 |
while (textComponentRefs != null && (c = textComponentRefs.get()) == null) { |
238 |
removeItem(textComponentRefs); |
239 |
} |
240 |
return c; |
241 |
} |
242 |
|
243 |
/** |
244 |
* Remove given entry and return a next one. |
245 |
*/ |
246 |
private static Item removeItem(Item item) { |
247 |
Item next = item.next; |
248 |
if (item.previous == null) { // Head |
249 |
assert (textComponentRefs == item); |
250 |
textComponentRefs = next; |
251 |
} else { // Not head |
252 |
item.previous.next = next; |
253 |
} |
254 |
if (next != null) |
255 |
next.previous = item.previous; |
256 |
item.next = item.previous = null; |
257 |
return next; |
258 |
} |
259 |
|
260 |
private static void moveToHead(Item item) { |
261 |
if (LOG.isLoggable(Level.FINEST)) { // Debugging |
262 |
isItemInList(item); |
263 |
} |
264 |
removeItem(item); |
265 |
item.next = textComponentRefs; |
266 |
if (textComponentRefs != null) |
267 |
textComponentRefs.previous = item; |
268 |
textComponentRefs = item; |
269 |
if (LOG.isLoggable(Level.FINEST)) { // Debugging |
270 |
isItemInList(item); |
271 |
checkItemListConsistency(); |
272 |
} |
273 |
} |
274 |
|
275 |
private static void firePropertyChange(String propertyName, Object oldValue, Object newValue) { |
276 |
pcs.firePropertyChange(propertyName, oldValue, newValue); |
277 |
} |
278 |
|
279 |
private static boolean isItemInList(Item item) { |
280 |
Item i = textComponentRefs; |
281 |
while (i != null) { |
282 |
if (i == item) |
283 |
return true; |
284 |
i = i.next; |
285 |
} |
286 |
return false; |
287 |
} |
288 |
|
289 |
private static void checkItemListConsistency() { |
290 |
Item item = textComponentRefs; |
291 |
Item previous = null; |
292 |
while (item != null) { |
293 |
assert item.previous == previous; |
294 |
previous = item; |
295 |
item = item.next; |
296 |
} |
297 |
if (previous != null) |
298 |
assert previous.next == null; |
299 |
} |
300 |
|
301 |
private static String dumpItemList() { |
302 |
StringBuilder sb = new StringBuilder(); |
303 |
int i = 0; |
304 |
Item item = textComponentRefs; |
305 |
while (item != null) { |
306 |
ArrayUtilities.appendBracketedIndex(sb, i, 1); |
307 |
sb.append(dumpComponent(item.get())); |
308 |
sb.append('\n'); |
309 |
item = item.next; |
310 |
i++; |
311 |
} |
312 |
sb.append('\n'); // One extra delimiting newline |
313 |
return sb.toString(); |
314 |
} |
315 |
|
316 |
private static String dumpComponent(JTextComponent c) { |
317 |
return "c[IHC=" + System.identityHashCode(c) |
318 |
+ "]=" + c; |
319 |
} |
320 |
|
321 |
/** |
322 |
* Item of a single linked list of text component references. |
323 |
*/ |
324 |
private static final class Item extends WeakReference<JTextComponent> { |
325 |
|
326 |
Item(JTextComponent c) { |
327 |
super(c); |
328 |
} |
329 |
|
330 |
Item next; |
331 |
|
332 |
Item previous; |
333 |
|
334 |
} |
335 |
|
336 |
private static final class FocusL implements FocusListener { |
337 |
|
338 |
static final FocusL INSTANCE = new FocusL(); |
339 |
|
340 |
public void focusGained(FocusEvent e) { |
341 |
EditorRegistry.focusGained((JTextComponent)e.getSource(), e.getOppositeComponent()); |
342 |
|
343 |
} |
344 |
|
345 |
public void focusLost(FocusEvent e) { |
346 |
EditorRegistry.focusLost((JTextComponent)e.getSource(), e.getOppositeComponent()); |
347 |
} |
348 |
|
349 |
} |
350 |
|
351 |
private static final class PropertyDocL implements PropertyChangeListener { |
352 |
|
353 |
static final PropertyDocL INSTANCE = new PropertyDocL(); |
354 |
|
355 |
public void propertyChange(PropertyChangeEvent evt) { |
356 |
if ("document".equals(evt.getPropertyName())) { |
357 |
focusedDocumentChange((JTextComponent)evt.getSource(), (Document)evt.getOldValue(), (Document)evt.getNewValue()); |
358 |
} |
359 |
} |
360 |
|
361 |
} |
362 |
|
363 |
} |