Class |
Line # |
Actions |
|||
---|---|---|---|---|---|
SwingUtils | 29 | 94 | 49 |
1 | /* | |
2 | * @(#)SwingUtils.java 1.02 11/15/08 | |
3 | * | |
4 | */ | |
5 | /* from https://github.com/tips4java/tips4java/blob/main/source/SwingUtils.java MIT License */ | |
6 | package darrylbu.util; | |
7 | ||
8 | import java.awt.Component; | |
9 | import java.awt.Container; | |
10 | import java.lang.reflect.InvocationTargetException; | |
11 | import java.lang.reflect.Method; | |
12 | import java.util.ArrayList; | |
13 | import java.util.Collections; | |
14 | import java.util.HashMap; | |
15 | import java.util.HashSet; | |
16 | import java.util.List; | |
17 | import java.util.Map; | |
18 | import java.util.Set; | |
19 | ||
20 | import javax.swing.JComponent; | |
21 | import javax.swing.UIDefaults; | |
22 | import javax.swing.UIManager; | |
23 | ||
24 | /** | |
25 | * A collection of utility methods for Swing. | |
26 | * | |
27 | * @author Darryl Burke | |
28 | */ | |
29 | public final class SwingUtils | |
30 | { | |
31 | ||
32 | 0 | private SwingUtils() |
33 | { | |
34 | 0 | throw new Error("SwingUtils is just a container for static methods"); |
35 | } | |
36 | ||
37 | /** | |
38 | * Convenience method for searching below <code>container</code> in the | |
39 | * component hierarchy and return nested components that are instances of | |
40 | * class <code>clazz</code> it finds. Returns an empty list if no such | |
41 | * components exist in the container. | |
42 | * <P> | |
43 | * Invoking this method with a class parameter of JComponent.class will return | |
44 | * all nested components. | |
45 | * <P> | |
46 | * This method invokes getDescendantsOfType(clazz, container, true) | |
47 | * | |
48 | * @param clazz | |
49 | * the class of components whose instances are to be found. | |
50 | * @param container | |
51 | * the container at which to begin the search | |
52 | * @return the List of components | |
53 | */ | |
54 | 0 | public static <T extends JComponent> List<T> getDescendantsOfType( |
55 | Class<T> clazz, Container container) | |
56 | { | |
57 | 0 | return getDescendantsOfType(clazz, container, true); |
58 | } | |
59 | ||
60 | /** | |
61 | * Convenience method for searching below <code>container</code> in the | |
62 | * component hierarchy and return nested components that are instances of | |
63 | * class <code>clazz</code> it finds. Returns an empty list if no such | |
64 | * components exist in the container. | |
65 | * <P> | |
66 | * Invoking this method with a class parameter of JComponent.class will return | |
67 | * all nested components. | |
68 | * | |
69 | * @param clazz | |
70 | * the class of components whose instances are to be found. | |
71 | * @param container | |
72 | * the container at which to begin the search | |
73 | * @param nested | |
74 | * true to list components nested within another listed component, | |
75 | * false otherwise | |
76 | * @return the List of components | |
77 | */ | |
78 | 0 | public static <T extends JComponent> List<T> getDescendantsOfType( |
79 | Class<T> clazz, Container container, boolean nested) | |
80 | { | |
81 | 0 | List<T> tList = new ArrayList<T>(); |
82 | 0 | for (Component component : container.getComponents()) |
83 | { | |
84 | 0 | if (clazz.isAssignableFrom(component.getClass())) |
85 | { | |
86 | 0 | tList.add(clazz.cast(component)); |
87 | } | |
88 | 0 | if (nested || !clazz.isAssignableFrom(component.getClass())) |
89 | { | |
90 | 0 | tList.addAll(SwingUtils.<T> getDescendantsOfType(clazz, |
91 | (Container) component, nested)); | |
92 | } | |
93 | } | |
94 | 0 | return tList; |
95 | } | |
96 | ||
97 | /** | |
98 | * Convenience method that searches below <code>container</code> in the | |
99 | * component hierarchy and returns the first found component that is an | |
100 | * instance of class <code>clazz</code> having the bound property value. | |
101 | * Returns {@code null} if such component cannot be found. | |
102 | * <P> | |
103 | * This method invokes getDescendantOfType(clazz, container, property, value, | |
104 | * true) | |
105 | * | |
106 | * @param clazz | |
107 | * the class of component whose instance is to be found. | |
108 | * @param container | |
109 | * the container at which to begin the search | |
110 | * @param property | |
111 | * the className of the bound property, exactly as expressed in the | |
112 | * accessor e.g. "Text" for getText(), "Value" for getValue(). | |
113 | * @param value | |
114 | * the value of the bound property | |
115 | * @return the component, or null if no such component exists in the container | |
116 | * @throws java.lang.IllegalArgumentException | |
117 | * if the bound property does not exist for the class or cannot be | |
118 | * accessed | |
119 | */ | |
120 | 0 | public static <T extends JComponent> T getDescendantOfType(Class<T> clazz, |
121 | Container container, String property, Object value) | |
122 | throws IllegalArgumentException | |
123 | { | |
124 | 0 | return getDescendantOfType(clazz, container, property, value, true); |
125 | } | |
126 | ||
127 | /** | |
128 | * Convenience method that searches below <code>container</code> in the | |
129 | * component hierarchy and returns the first found component that is an | |
130 | * instance of class <code>clazz</code> and has the bound property value. | |
131 | * Returns {@code null} if such component cannot be found. | |
132 | * | |
133 | * @param clazz | |
134 | * the class of component whose instance to be found. | |
135 | * @param container | |
136 | * the container at which to begin the search | |
137 | * @param property | |
138 | * the className of the bound property, exactly as expressed in the | |
139 | * accessor e.g. "Text" for getText(), "Value" for getValue(). | |
140 | * @param value | |
141 | * the value of the bound property | |
142 | * @param nested | |
143 | * true to list components nested within another component which is | |
144 | * also an instance of <code>clazz</code>, false otherwise | |
145 | * @return the component, or null if no such component exists in the container | |
146 | * @throws java.lang.IllegalArgumentException | |
147 | * if the bound property does not exist for the class or cannot be | |
148 | * accessed | |
149 | */ | |
150 | 0 | public static <T extends JComponent> T getDescendantOfType(Class<T> clazz, |
151 | Container container, String property, Object value, | |
152 | boolean nested) throws IllegalArgumentException | |
153 | { | |
154 | 0 | List<T> list = getDescendantsOfType(clazz, container, nested); |
155 | 0 | return getComponentFromList(clazz, list, property, value); |
156 | } | |
157 | ||
158 | /** | |
159 | * Convenience method for searching below <code>container</code> in the | |
160 | * component hierarchy and return nested components of class | |
161 | * <code>clazz</code> it finds. Returns an empty list if no such components | |
162 | * exist in the container. | |
163 | * <P> | |
164 | * This method invokes getDescendantsOfClass(clazz, container, true) | |
165 | * | |
166 | * @param clazz | |
167 | * the class of components to be found. | |
168 | * @param container | |
169 | * the container at which to begin the search | |
170 | * @return the List of components | |
171 | */ | |
172 | 0 | public static <T extends JComponent> List<T> getDescendantsOfClass( |
173 | Class<T> clazz, Container container) | |
174 | { | |
175 | 0 | return getDescendantsOfClass(clazz, container, true); |
176 | } | |
177 | ||
178 | /** | |
179 | * Convenience method for searching below <code>container</code> in the | |
180 | * component hierarchy and return nested components of class | |
181 | * <code>clazz</code> it finds. Returns an empty list if no such components | |
182 | * exist in the container. | |
183 | * | |
184 | * @param clazz | |
185 | * the class of components to be found. | |
186 | * @param container | |
187 | * the container at which to begin the search | |
188 | * @param nested | |
189 | * true to list components nested within another listed component, | |
190 | * false otherwise | |
191 | * @return the List of components | |
192 | */ | |
193 | 0 | public static <T extends JComponent> List<T> getDescendantsOfClass( |
194 | Class<T> clazz, Container container, boolean nested) | |
195 | { | |
196 | 0 | List<T> tList = new ArrayList<T>(); |
197 | 0 | for (Component component : container.getComponents()) |
198 | { | |
199 | 0 | if (clazz.equals(component.getClass())) |
200 | { | |
201 | 0 | tList.add(clazz.cast(component)); |
202 | } | |
203 | 0 | if (nested || !clazz.equals(component.getClass())) |
204 | { | |
205 | 0 | tList.addAll(SwingUtils.<T> getDescendantsOfClass(clazz, |
206 | (Container) component, nested)); | |
207 | } | |
208 | } | |
209 | 0 | return tList; |
210 | } | |
211 | ||
212 | /** | |
213 | * Convenience method that searches below <code>container</code> in the | |
214 | * component hierarchy in a depth first manner and returns the first found | |
215 | * component of class <code>clazz</code> having the bound property value. | |
216 | * <P> | |
217 | * Returns {@code null} if such component cannot be found. | |
218 | * <P> | |
219 | * This method invokes getDescendantOfClass(clazz, container, property, value, | |
220 | * true) | |
221 | * | |
222 | * @param clazz | |
223 | * the class of component to be found. | |
224 | * @param container | |
225 | * the container at which to begin the search | |
226 | * @param property | |
227 | * the className of the bound property, exactly as expressed in the | |
228 | * accessor e.g. "Text" for getText(), "Value" for getValue(). This | |
229 | * parameter is case sensitive. | |
230 | * @param value | |
231 | * the value of the bound property | |
232 | * @return the component, or null if no such component exists in the | |
233 | * container's hierarchy. | |
234 | * @throws java.lang.IllegalArgumentException | |
235 | * if the bound property does not exist for the class or cannot be | |
236 | * accessed | |
237 | */ | |
238 | 0 | public static <T extends JComponent> T getDescendantOfClass( |
239 | Class<T> clazz, Container container, String property, | |
240 | Object value) throws IllegalArgumentException | |
241 | { | |
242 | 0 | return getDescendantOfClass(clazz, container, property, value, true); |
243 | } | |
244 | ||
245 | /** | |
246 | * Convenience method that searches below <code>container</code> in the | |
247 | * component hierarchy in a depth first manner and returns the first found | |
248 | * component of class <code>clazz</code> having the bound property value. | |
249 | * <P> | |
250 | * Returns {@code null} if such component cannot be found. | |
251 | * | |
252 | * @param clazz | |
253 | * the class of component to be found. | |
254 | * @param container | |
255 | * the container at which to begin the search | |
256 | * @param property | |
257 | * the className of the bound property, exactly as expressed in the | |
258 | * accessor e.g. "Text" for getText(), "Value" for getValue(). This | |
259 | * parameter is case sensitive. | |
260 | * @param value | |
261 | * the value of the bound property | |
262 | * @param nested | |
263 | * true to include components nested within another listed component, | |
264 | * false otherwise | |
265 | * @return the component, or null if no such component exists in the | |
266 | * container's hierarchy | |
267 | * @throws java.lang.IllegalArgumentException | |
268 | * if the bound property does not exist for the class or cannot be | |
269 | * accessed | |
270 | */ | |
271 | 0 | public static <T extends JComponent> T getDescendantOfClass( |
272 | Class<T> clazz, Container container, String property, | |
273 | Object value, boolean nested) throws IllegalArgumentException | |
274 | { | |
275 | 0 | List<T> list = getDescendantsOfClass(clazz, container, nested); |
276 | 0 | return getComponentFromList(clazz, list, property, value); |
277 | } | |
278 | ||
279 | 0 | private static <T extends JComponent> T getComponentFromList( |
280 | Class<T> clazz, List<T> list, String property, Object value) | |
281 | throws IllegalArgumentException | |
282 | { | |
283 | 0 | T retVal = null; |
284 | 0 | Method method = null; |
285 | 0 | try |
286 | { | |
287 | 0 | method = clazz.getMethod("get" + property); |
288 | } catch (NoSuchMethodException ex) | |
289 | { | |
290 | 0 | try |
291 | { | |
292 | 0 | method = clazz.getMethod("is" + property); |
293 | } catch (NoSuchMethodException ex1) | |
294 | { | |
295 | 0 | throw new IllegalArgumentException("Property " + property |
296 | + " not found in class " + clazz.getName()); | |
297 | } | |
298 | } | |
299 | 0 | try |
300 | { | |
301 | 0 | for (T t : list) |
302 | { | |
303 | 0 | Object testVal = method.invoke(t); |
304 | 0 | if (equals(value, testVal)) |
305 | { | |
306 | 0 | return t; |
307 | } | |
308 | } | |
309 | } catch (InvocationTargetException ex) | |
310 | { | |
311 | 0 | throw new IllegalArgumentException("Error accessing property " |
312 | + property + " in class " + clazz.getName()); | |
313 | } catch (IllegalAccessException ex) | |
314 | { | |
315 | 0 | throw new IllegalArgumentException("Property " + property |
316 | + " cannot be accessed in class " + clazz.getName()); | |
317 | } catch (SecurityException ex) | |
318 | { | |
319 | 0 | throw new IllegalArgumentException("Property " + property |
320 | + " cannot be accessed in class " + clazz.getName()); | |
321 | } | |
322 | 0 | return retVal; |
323 | } | |
324 | ||
325 | /** | |
326 | * Convenience method for determining whether two objects are either equal or | |
327 | * both null. | |
328 | * | |
329 | * @param obj1 | |
330 | * the first reference object to compare. | |
331 | * @param obj2 | |
332 | * the second reference object to compare. | |
333 | * @return true if obj1 and obj2 are equal or if both are null, false | |
334 | * otherwise | |
335 | */ | |
336 | 0 | public static boolean equals(Object obj1, Object obj2) |
337 | { | |
338 | 0 | return obj1 == null ? obj2 == null : obj1.equals(obj2); |
339 | } | |
340 | ||
341 | /** | |
342 | * Convenience method for mapping a container in the hierarchy to its | |
343 | * contained components. The keys are the containers, and the values are lists | |
344 | * of contained components. | |
345 | * <P> | |
346 | * Implementation note: The returned value is a HashMap and the values are of | |
347 | * type ArrayList. This is subject to change, so callers should code against | |
348 | * the interfaces Map and List. | |
349 | * | |
350 | * @param container | |
351 | * The JComponent to be mapped | |
352 | * @param nested | |
353 | * true to drill down to nested containers, false otherwise | |
354 | * @return the Map of the UI | |
355 | */ | |
356 | 0 | public static Map<JComponent, List<JComponent>> getComponentMap( |
357 | JComponent container, boolean nested) | |
358 | { | |
359 | 0 | HashMap<JComponent, List<JComponent>> retVal = new HashMap<JComponent, List<JComponent>>(); |
360 | 0 | for (JComponent component : getDescendantsOfType(JComponent.class, |
361 | container, false)) | |
362 | { | |
363 | 0 | if (!retVal.containsKey(container)) |
364 | { | |
365 | 0 | retVal.put(container, new ArrayList<JComponent>()); |
366 | } | |
367 | 0 | retVal.get(container).add(component); |
368 | 0 | if (nested) |
369 | { | |
370 | 0 | retVal.putAll(getComponentMap(component, nested)); |
371 | } | |
372 | } | |
373 | 0 | return retVal; |
374 | } | |
375 | ||
376 | /** | |
377 | * Convenience method for retrieving a subset of the UIDefaults pertaining to | |
378 | * a particular class. | |
379 | * | |
380 | * @param clazz | |
381 | * the class of interest | |
382 | * @return the UIDefaults of the class | |
383 | */ | |
384 | 0 | public static UIDefaults getUIDefaultsOfClass(Class clazz) |
385 | { | |
386 | 0 | String name = clazz.getName(); |
387 | 0 | name = name.substring(name.lastIndexOf(".") + 2); |
388 | 0 | return getUIDefaultsOfClass(name); |
389 | } | |
390 | ||
391 | /** | |
392 | * Convenience method for retrieving a subset of the UIDefaults pertaining to | |
393 | * a particular class. | |
394 | * | |
395 | * @param className | |
396 | * fully qualified name of the class of interest | |
397 | * @return the UIDefaults of the class named | |
398 | */ | |
399 | 0 | public static UIDefaults getUIDefaultsOfClass(String className) |
400 | { | |
401 | 0 | UIDefaults retVal = new UIDefaults(); |
402 | 0 | UIDefaults defaults = UIManager.getLookAndFeelDefaults(); |
403 | 0 | List<?> listKeys = Collections.list(defaults.keys()); |
404 | 0 | for (Object key : listKeys) |
405 | { | |
406 | 0 | if (key instanceof String && ((String) key).startsWith(className)) |
407 | { | |
408 | 0 | String stringKey = (String) key; |
409 | 0 | String property = stringKey; |
410 | 0 | if (stringKey.contains(".")) |
411 | { | |
412 | 0 | property = stringKey.substring(stringKey.indexOf(".") + 1); |
413 | } | |
414 | 0 | retVal.put(property, defaults.get(key)); |
415 | } | |
416 | } | |
417 | 0 | return retVal; |
418 | } | |
419 | ||
420 | /** | |
421 | * Convenience method for retrieving the UIDefault for a single property of a | |
422 | * particular class. | |
423 | * | |
424 | * @param clazz | |
425 | * the class of interest | |
426 | * @param property | |
427 | * the property to query | |
428 | * @return the UIDefault property, or null if not found | |
429 | */ | |
430 | 0 | public static Object getUIDefaultOfClass(Class clazz, String property) |
431 | { | |
432 | 0 | Object retVal = null; |
433 | 0 | UIDefaults defaults = getUIDefaultsOfClass(clazz); |
434 | 0 | List<Object> listKeys = Collections.list(defaults.keys()); |
435 | 0 | for (Object key : listKeys) |
436 | { | |
437 | 0 | if (key.equals(property)) |
438 | { | |
439 | 0 | return defaults.get(key); |
440 | } | |
441 | 0 | if (key.toString().equalsIgnoreCase(property)) |
442 | { | |
443 | 0 | retVal = defaults.get(key); |
444 | } | |
445 | } | |
446 | 0 | return retVal; |
447 | } | |
448 | ||
449 | /** | |
450 | * Exclude methods that return values that are meaningless to the user | |
451 | */ | |
452 | static Set<String> setExclude = new HashSet<String>(); | |
453 | 0 | static |
454 | { | |
455 | 0 | setExclude.add("getFocusCycleRootAncestor"); |
456 | 0 | setExclude.add("getAccessibleContext"); |
457 | 0 | setExclude.add("getColorModel"); |
458 | 0 | setExclude.add("getGraphics"); |
459 | 0 | setExclude.add("getGraphicsConfiguration"); |
460 | } | |
461 | ||
462 | /** | |
463 | * Convenience method for obtaining most non-null human readable properties of | |
464 | * a JComponent. Array properties are not included. | |
465 | * <P> | |
466 | * Implementation note: The returned value is a HashMap. This is subject to | |
467 | * change, so callers should code against the interface Map. | |
468 | * | |
469 | * @param component | |
470 | * the component whose proerties are to be determined | |
471 | * @return the class and value of the properties | |
472 | */ | |
473 | 0 | public static Map<Object, Object> getProperties(JComponent component) |
474 | { | |
475 | 0 | Map<Object, Object> retVal = new HashMap<Object, Object>(); |
476 | 0 | Class<?> clazz = component.getClass(); |
477 | 0 | Method[] methods = clazz.getMethods(); |
478 | 0 | Object value = null; |
479 | 0 | for (Method method : methods) |
480 | { | |
481 | 0 | if (method.getName().matches("^(is|get).*") |
482 | && method.getParameterTypes().length == 0) | |
483 | { | |
484 | 0 | try |
485 | { | |
486 | 0 | Class returnType = method.getReturnType(); |
487 | 0 | if (returnType != void.class |
488 | && !returnType.getName().startsWith("[") | |
489 | && !setExclude.contains(method.getName())) | |
490 | { | |
491 | 0 | String key = method.getName(); |
492 | 0 | value = method.invoke(component); |
493 | 0 | if (value != null && !(value instanceof Component)) |
494 | { | |
495 | 0 | retVal.put(key, value); |
496 | } | |
497 | } | |
498 | // ignore exceptions that arise if the property could not be accessed | |
499 | } catch (IllegalAccessException ex) | |
500 | { | |
501 | } catch (IllegalArgumentException ex) | |
502 | { | |
503 | } catch (InvocationTargetException ex) | |
504 | { | |
505 | } | |
506 | } | |
507 | } | |
508 | 0 | return retVal; |
509 | } | |
510 | ||
511 | /** | |
512 | * Convenience method to obtain the Swing class from which this component was | |
513 | * directly or indirectly derived. | |
514 | * | |
515 | * @param component | |
516 | * The component whose Swing superclass is to be determined | |
517 | * @return The nearest Swing class in the inheritance tree | |
518 | */ | |
519 | 0 | public static <T extends JComponent> Class getJClass(T component) |
520 | { | |
521 | 0 | Class<?> clazz = component.getClass(); |
522 | 0 | while (!clazz.getName().matches("javax.swing.J[^.]*$")) |
523 | { | |
524 | 0 | clazz = clazz.getSuperclass(); |
525 | } | |
526 | 0 | return clazz; |
527 | } | |
528 | } |