--- /dev/null
+/*
+ * @(#)SwingUtils.java 1.02 11/15/08
+ *
+ */
+/* from https://github.com/tips4java/tips4java/blob/main/source/SwingUtils.java MIT License */
+//package darrylbu.util;
+package jalview.util;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.JComponent;
+import javax.swing.UIDefaults;
+import javax.swing.UIManager;
+
+/**
+ * A collection of utility methods for Swing.
+ *
+ * @author Darryl Burke
+ */
+public final class SwingUtils
+{
+
+ private SwingUtils()
+ {
+ throw new Error("SwingUtils is just a container for static methods");
+ }
+
+ /**
+ * Convenience method for searching below <code>container</code> in the
+ * component hierarchy and return nested components that are instances of
+ * class <code>clazz</code> it finds. Returns an empty list if no such
+ * components exist in the container.
+ * <P>
+ * Invoking this method with a class parameter of JComponent.class will return
+ * all nested components.
+ * <P>
+ * This method invokes getDescendantsOfType(clazz, container, true)
+ *
+ * @param clazz
+ * the class of components whose instances are to be found.
+ * @param container
+ * the container at which to begin the search
+ * @return the List of components
+ */
+ public static <T extends JComponent> List<T> getDescendantsOfType(
+ Class<T> clazz, Container container)
+ {
+ return getDescendantsOfType(clazz, container, true);
+ }
+
+ /**
+ * Convenience method for searching below <code>container</code> in the
+ * component hierarchy and return nested components that are instances of
+ * class <code>clazz</code> it finds. Returns an empty list if no such
+ * components exist in the container.
+ * <P>
+ * Invoking this method with a class parameter of JComponent.class will return
+ * all nested components.
+ *
+ * @param clazz
+ * the class of components whose instances are to be found.
+ * @param container
+ * the container at which to begin the search
+ * @param nested
+ * true to list components nested within another listed component,
+ * false otherwise
+ * @return the List of components
+ */
+ public static <T extends JComponent> List<T> getDescendantsOfType(
+ Class<T> clazz, Container container, boolean nested)
+ {
+ List<T> tList = new ArrayList<T>();
+ for (Component component : container.getComponents())
+ {
+ if (clazz.isAssignableFrom(component.getClass()))
+ {
+ tList.add(clazz.cast(component));
+ }
+ if (nested || !clazz.isAssignableFrom(component.getClass()))
+ {
+ tList.addAll(SwingUtils.<T> getDescendantsOfType(clazz,
+ (Container) component, nested));
+ }
+ }
+ return tList;
+ }
+
+ /**
+ * Convenience method that searches below <code>container</code> in the
+ * component hierarchy and returns the first found component that is an
+ * instance of class <code>clazz</code> having the bound property value.
+ * Returns {@code null} if such component cannot be found.
+ * <P>
+ * This method invokes getDescendantOfType(clazz, container, property, value,
+ * true)
+ *
+ * @param clazz
+ * the class of component whose instance is to be found.
+ * @param container
+ * the container at which to begin the search
+ * @param property
+ * the className of the bound property, exactly as expressed in the
+ * accessor e.g. "Text" for getText(), "Value" for getValue().
+ * @param value
+ * the value of the bound property
+ * @return the component, or null if no such component exists in the container
+ * @throws java.lang.IllegalArgumentException
+ * if the bound property does not exist for the class or cannot be
+ * accessed
+ */
+ public static <T extends JComponent> T getDescendantOfType(Class<T> clazz,
+ Container container, String property, Object value)
+ throws IllegalArgumentException
+ {
+ return getDescendantOfType(clazz, container, property, value, true);
+ }
+
+ /**
+ * Convenience method that searches below <code>container</code> in the
+ * component hierarchy and returns the first found component that is an
+ * instance of class <code>clazz</code> and has the bound property value.
+ * Returns {@code null} if such component cannot be found.
+ *
+ * @param clazz
+ * the class of component whose instance to be found.
+ * @param container
+ * the container at which to begin the search
+ * @param property
+ * the className of the bound property, exactly as expressed in the
+ * accessor e.g. "Text" for getText(), "Value" for getValue().
+ * @param value
+ * the value of the bound property
+ * @param nested
+ * true to list components nested within another component which is
+ * also an instance of <code>clazz</code>, false otherwise
+ * @return the component, or null if no such component exists in the container
+ * @throws java.lang.IllegalArgumentException
+ * if the bound property does not exist for the class or cannot be
+ * accessed
+ */
+ public static <T extends JComponent> T getDescendantOfType(Class<T> clazz,
+ Container container, String property, Object value,
+ boolean nested) throws IllegalArgumentException
+ {
+ List<T> list = getDescendantsOfType(clazz, container, nested);
+ return getComponentFromList(clazz, list, property, value);
+ }
+
+ /**
+ * Convenience method for searching below <code>container</code> in the
+ * component hierarchy and return nested components of class
+ * <code>clazz</code> it finds. Returns an empty list if no such components
+ * exist in the container.
+ * <P>
+ * This method invokes getDescendantsOfClass(clazz, container, true)
+ *
+ * @param clazz
+ * the class of components to be found.
+ * @param container
+ * the container at which to begin the search
+ * @return the List of components
+ */
+ public static <T extends JComponent> List<T> getDescendantsOfClass(
+ Class<T> clazz, Container container)
+ {
+ return getDescendantsOfClass(clazz, container, true);
+ }
+
+ /**
+ * Convenience method for searching below <code>container</code> in the
+ * component hierarchy and return nested components of class
+ * <code>clazz</code> it finds. Returns an empty list if no such components
+ * exist in the container.
+ *
+ * @param clazz
+ * the class of components to be found.
+ * @param container
+ * the container at which to begin the search
+ * @param nested
+ * true to list components nested within another listed component,
+ * false otherwise
+ * @return the List of components
+ */
+ public static <T extends JComponent> List<T> getDescendantsOfClass(
+ Class<T> clazz, Container container, boolean nested)
+ {
+ List<T> tList = new ArrayList<T>();
+ for (Component component : container.getComponents())
+ {
+ if (clazz.equals(component.getClass()))
+ {
+ tList.add(clazz.cast(component));
+ }
+ if (nested || !clazz.equals(component.getClass()))
+ {
+ tList.addAll(SwingUtils.<T> getDescendantsOfClass(clazz,
+ (Container) component, nested));
+ }
+ }
+ return tList;
+ }
+
+ /**
+ * Convenience method that searches below <code>container</code> in the
+ * component hierarchy in a depth first manner and returns the first found
+ * component of class <code>clazz</code> having the bound property value.
+ * <P>
+ * Returns {@code null} if such component cannot be found.
+ * <P>
+ * This method invokes getDescendantOfClass(clazz, container, property, value,
+ * true)
+ *
+ * @param clazz
+ * the class of component to be found.
+ * @param container
+ * the container at which to begin the search
+ * @param property
+ * the className of the bound property, exactly as expressed in the
+ * accessor e.g. "Text" for getText(), "Value" for getValue(). This
+ * parameter is case sensitive.
+ * @param value
+ * the value of the bound property
+ * @return the component, or null if no such component exists in the
+ * container's hierarchy.
+ * @throws java.lang.IllegalArgumentException
+ * if the bound property does not exist for the class or cannot be
+ * accessed
+ */
+ public static <T extends JComponent> T getDescendantOfClass(
+ Class<T> clazz, Container container, String property,
+ Object value) throws IllegalArgumentException
+ {
+ return getDescendantOfClass(clazz, container, property, value, true);
+ }
+
+ /**
+ * Convenience method that searches below <code>container</code> in the
+ * component hierarchy in a depth first manner and returns the first found
+ * component of class <code>clazz</code> having the bound property value.
+ * <P>
+ * Returns {@code null} if such component cannot be found.
+ *
+ * @param clazz
+ * the class of component to be found.
+ * @param container
+ * the container at which to begin the search
+ * @param property
+ * the className of the bound property, exactly as expressed in the
+ * accessor e.g. "Text" for getText(), "Value" for getValue(). This
+ * parameter is case sensitive.
+ * @param value
+ * the value of the bound property
+ * @param nested
+ * true to include components nested within another listed component,
+ * false otherwise
+ * @return the component, or null if no such component exists in the
+ * container's hierarchy
+ * @throws java.lang.IllegalArgumentException
+ * if the bound property does not exist for the class or cannot be
+ * accessed
+ */
+ public static <T extends JComponent> T getDescendantOfClass(
+ Class<T> clazz, Container container, String property,
+ Object value, boolean nested) throws IllegalArgumentException
+ {
+ List<T> list = getDescendantsOfClass(clazz, container, nested);
+ return getComponentFromList(clazz, list, property, value);
+ }
+
+ private static <T extends JComponent> T getComponentFromList(
+ Class<T> clazz, List<T> list, String property, Object value)
+ throws IllegalArgumentException
+ {
+ T retVal = null;
+ Method method = null;
+ try
+ {
+ method = clazz.getMethod("get" + property);
+ } catch (NoSuchMethodException ex)
+ {
+ try
+ {
+ method = clazz.getMethod("is" + property);
+ } catch (NoSuchMethodException ex1)
+ {
+ throw new IllegalArgumentException("Property " + property
+ + " not found in class " + clazz.getName());
+ }
+ }
+ try
+ {
+ for (T t : list)
+ {
+ Object testVal = method.invoke(t);
+ if (equals(value, testVal))
+ {
+ return t;
+ }
+ }
+ } catch (InvocationTargetException ex)
+ {
+ throw new IllegalArgumentException("Error accessing property "
+ + property + " in class " + clazz.getName());
+ } catch (IllegalAccessException ex)
+ {
+ throw new IllegalArgumentException("Property " + property
+ + " cannot be accessed in class " + clazz.getName());
+ } catch (SecurityException ex)
+ {
+ throw new IllegalArgumentException("Property " + property
+ + " cannot be accessed in class " + clazz.getName());
+ }
+ return retVal;
+ }
+
+ /**
+ * Convenience method for determining whether two objects are either equal or
+ * both null.
+ *
+ * @param obj1
+ * the first reference object to compare.
+ * @param obj2
+ * the second reference object to compare.
+ * @return true if obj1 and obj2 are equal or if both are null, false
+ * otherwise
+ */
+ public static boolean equals(Object obj1, Object obj2)
+ {
+ return obj1 == null ? obj2 == null : obj1.equals(obj2);
+ }
+
+ /**
+ * Convenience method for mapping a container in the hierarchy to its
+ * contained components. The keys are the containers, and the values are lists
+ * of contained components.
+ * <P>
+ * Implementation note: The returned value is a HashMap and the values are of
+ * type ArrayList. This is subject to change, so callers should code against
+ * the interfaces Map and List.
+ *
+ * @param container
+ * The JComponent to be mapped
+ * @param nested
+ * true to drill down to nested containers, false otherwise
+ * @return the Map of the UI
+ */
+ public static Map<JComponent, List<JComponent>> getComponentMap(
+ JComponent container, boolean nested)
+ {
+ HashMap<JComponent, List<JComponent>> retVal = new HashMap<JComponent, List<JComponent>>();
+ for (JComponent component : getDescendantsOfType(JComponent.class,
+ container, false))
+ {
+ if (!retVal.containsKey(container))
+ {
+ retVal.put(container, new ArrayList<JComponent>());
+ }
+ retVal.get(container).add(component);
+ if (nested)
+ {
+ retVal.putAll(getComponentMap(component, nested));
+ }
+ }
+ return retVal;
+ }
+
+ /**
+ * Convenience method for retrieving a subset of the UIDefaults pertaining to
+ * a particular class.
+ *
+ * @param clazz
+ * the class of interest
+ * @return the UIDefaults of the class
+ */
+ public static UIDefaults getUIDefaultsOfClass(Class clazz)
+ {
+ String name = clazz.getName();
+ name = name.substring(name.lastIndexOf(".") + 2);
+ return getUIDefaultsOfClass(name);
+ }
+
+ /**
+ * Convenience method for retrieving a subset of the UIDefaults pertaining to
+ * a particular class.
+ *
+ * @param className
+ * fully qualified name of the class of interest
+ * @return the UIDefaults of the class named
+ */
+ public static UIDefaults getUIDefaultsOfClass(String className)
+ {
+ UIDefaults retVal = new UIDefaults();
+ UIDefaults defaults = UIManager.getLookAndFeelDefaults();
+ List<?> listKeys = Collections.list(defaults.keys());
+ for (Object key : listKeys)
+ {
+ if (key instanceof String && ((String) key).startsWith(className))
+ {
+ String stringKey = (String) key;
+ String property = stringKey;
+ if (stringKey.contains("."))
+ {
+ property = stringKey.substring(stringKey.indexOf(".") + 1);
+ }
+ retVal.put(property, defaults.get(key));
+ }
+ }
+ return retVal;
+ }
+
+ /**
+ * Convenience method for retrieving the UIDefault for a single property of a
+ * particular class.
+ *
+ * @param clazz
+ * the class of interest
+ * @param property
+ * the property to query
+ * @return the UIDefault property, or null if not found
+ */
+ public static Object getUIDefaultOfClass(Class clazz, String property)
+ {
+ Object retVal = null;
+ UIDefaults defaults = getUIDefaultsOfClass(clazz);
+ List<Object> listKeys = Collections.list(defaults.keys());
+ for (Object key : listKeys)
+ {
+ if (key.equals(property))
+ {
+ return defaults.get(key);
+ }
+ if (key.toString().equalsIgnoreCase(property))
+ {
+ retVal = defaults.get(key);
+ }
+ }
+ return retVal;
+ }
+
+ /**
+ * Exclude methods that return values that are meaningless to the user
+ */
+ static Set<String> setExclude = new HashSet<String>();
+ static
+ {
+ setExclude.add("getFocusCycleRootAncestor");
+ setExclude.add("getAccessibleContext");
+ setExclude.add("getColorModel");
+ setExclude.add("getGraphics");
+ setExclude.add("getGraphicsConfiguration");
+ }
+
+ /**
+ * Convenience method for obtaining most non-null human readable properties of
+ * a JComponent. Array properties are not included.
+ * <P>
+ * Implementation note: The returned value is a HashMap. This is subject to
+ * change, so callers should code against the interface Map.
+ *
+ * @param component
+ * the component whose proerties are to be determined
+ * @return the class and value of the properties
+ */
+ public static Map<Object, Object> getProperties(JComponent component)
+ {
+ Map<Object, Object> retVal = new HashMap<Object, Object>();
+ Class<?> clazz = component.getClass();
+ Method[] methods = clazz.getMethods();
+ Object value = null;
+ for (Method method : methods)
+ {
+ if (method.getName().matches("^(is|get).*")
+ && method.getParameterTypes().length == 0)
+ {
+ try
+ {
+ Class returnType = method.getReturnType();
+ if (returnType != void.class
+ && !returnType.getName().startsWith("[")
+ && !setExclude.contains(method.getName()))
+ {
+ String key = method.getName();
+ value = method.invoke(component);
+ if (value != null && !(value instanceof Component))
+ {
+ retVal.put(key, value);
+ }
+ }
+ // ignore exceptions that arise if the property could not be accessed
+ } catch (IllegalAccessException ex)
+ {
+ } catch (IllegalArgumentException ex)
+ {
+ } catch (InvocationTargetException ex)
+ {
+ }
+ }
+ }
+ return retVal;
+ }
+
+ /**
+ * Convenience method to obtain the Swing class from which this component was
+ * directly or indirectly derived.
+ *
+ * @param component
+ * The component whose Swing superclass is to be determined
+ * @return The nearest Swing class in the inheritance tree
+ */
+ public static <T extends JComponent> Class getJClass(T component)
+ {
+ Class<?> clazz = component.getClass();
+ while (!clazz.getName().matches("javax.swing.J[^.]*$"))
+ {
+ clazz = clazz.getSuperclass();
+ }
+ return clazz;
+ }
+}