diff --git a/src/share/classes/javax/swing/JLayer.java b/src/share/classes/javax/swing/JLayer.java new file mode 100644 index 0000000000000000000000000000000000000000..b8cc69e9bcd1086dccd072b7ad82838310ac60ef --- /dev/null +++ b/src/share/classes/javax/swing/JLayer.java @@ -0,0 +1,788 @@ +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ + +package javax.swing; + +import javax.swing.plaf.LayerUI; +import java.awt.*; +import java.awt.event.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Iterator; +import java.security.AccessController; +import java.security.PrivilegedAction; + +/** + * {@code JLayer} is a universal decorator for Swing components + * which enables you to implement various advanced painting effects as well as + * receive notifications of all {@code AWTEvent}s generated within its borders. + *

+ * {@code JLayer} delegates the handling of painting and input events to a + * {@link javax.swing.plaf.LayerUI} object, which performs the actual decoration. + *

+ * The custom painting implemented in the {@code LayerUI} and events notification + * work for the JLayer itself and all its subcomponents. + * This combination enables you to enrich existing components + * by adding new advanced functionality such as temporary locking of a hierarchy, + * data tips for compound components, enhanced mouse scrolling etc and so on. + *

+ * {@code JLayer} is a good solution if you only need to do custom painting + * over compound component or catch input events from its subcomponents. + *

+ *         // create a component to be decorated with the layer
+ *        JPanel panel = new JPanel();
+ *        panel.add(new JButton("JButton"));
+ *        // This custom layerUI will fill the layer with translucent green
+ *        // and print out all mouseMotion events generated within its borders
+ *        LayerUI<JPanel> layerUI = new LayerUI<JPanel>() {
+ *            public void paint(Graphics g, JCompo  nent c) {
+ *                // paint the layer as is
+ *                super.paint(g, c);
+ *                // fill it with the translucent green
+ *                g.setColor(new Color(0, 128, 0, 128));
+ *                g.fillRect(0, 0, c.getWidth(), c.getHeight());
+ *            }
+ *            // overridden method which catches MouseMotion events
+ *            public void eventDispatched(AWTEvent e, JLayer<JPanel> l) {
+ *                System.out.println("AWTEvent detected: " + e);
+ *            }
+ *        };
+ *        // create the layer for the panel using our custom layerUI
+ *        JLayer<JPanel> layer = new JLayer<JPanel>(panel, layerUI);
+ *        // work with the layer as with any other Swing component
+ *        frame.add(layer);
+ * 
+ * + * Note: {@code JLayer} doesn't support the following methods: + * + * using any of of them will cause {@code UnsupportedOperationException} to be thrown, + * to add a component to {@code JLayer} + * use {@link #setView(Component)} or {@link #setGlassPane(JPanel)}. + * + * @param the type of {@code JLayer}'s view component + * + * @see #JLayer(Component) + * @see #setView(Component) + * @see #getView() + * @see javax.swing.plaf.LayerUI + * @see #JLayer(Component, LayerUI) + * @see #setUI(javax.swing.plaf.LayerUI) + * @see #getUI() + * @since 1.7 + * + * @author Alexander Potochkin + */ +public final class JLayer + extends JComponent + implements Scrollable, PropertyChangeListener { + private V view; + // this field is necessary because JComponent.ui is transient + // when layerUI is serializable + private LayerUI layerUI; + private JPanel glassPane; + private boolean isPainting; + private static final DefaultLayerLayout sharedLayoutInstance = + new DefaultLayerLayout(); + private long eventMask; + + private static final LayerEventController eventController = + new LayerEventController(); + + private static final long ACCEPTED_EVENTS = + AWTEvent.COMPONENT_EVENT_MASK | + AWTEvent.CONTAINER_EVENT_MASK | + AWTEvent.FOCUS_EVENT_MASK | + AWTEvent.KEY_EVENT_MASK | + AWTEvent.MOUSE_WHEEL_EVENT_MASK | + AWTEvent.MOUSE_MOTION_EVENT_MASK | + AWTEvent.MOUSE_EVENT_MASK | + AWTEvent.INPUT_METHOD_EVENT_MASK | + AWTEvent.HIERARCHY_EVENT_MASK | + AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK; + + /** + * Creates a new {@code JLayer} object with a {@code null} view component + * and {@code null} {@link javax.swing.plaf.LayerUI}. + * + * @see #setView + * @see #setUI + */ + public JLayer() { + this(null); + } + + /** + * Creates a new {@code JLayer} object + * with {@code null} {@link javax.swing.plaf.LayerUI}. + * + * @param view the component to be decorated by this {@code JLayer} + * + * @see #setUI + */ + public JLayer(V view) { + this(view, null); + } + + /** + * Creates a new {@code JLayer} object with the specified view component + * and {@link javax.swing.plaf.LayerUI} object. + * + * @param view the component to be decorated + * @param ui the {@link javax.swing.plaf.LayerUI} delegate + * to be used by this {@code JLayer} + */ + public JLayer(V view, LayerUI ui) { + setLayout(sharedLayoutInstance); + setGlassPane(createGlassPane()); + setView(view); + setUI(ui); + } + + /** + * Returns the {@code JLayer}'s view component or {@code null}. + *
This is a bound property. + * + * @return the {@code JLayer}'s view component + * or {@code null} if none exists + * + * @see #setView(V) + */ + public V getView() { + return view; + } + + /** + * Sets the {@code JLayer}'s view component, which can be {@code null}. + *
This is a bound property. + * + * @param view the view component for this {@code JLayer} + * + * @see #getView() + */ + public void setView(V view) { + Component oldView = getView(); + if (oldView != null) { + super.remove(oldView); + } + if (view != null) { + super.addImpl(view, null, getComponentCount()); + } + this.view = view; + firePropertyChange("view", oldView, view); + revalidate(); + repaint(); + } + + /** + * Sets the {@link javax.swing.plaf.LayerUI} which will perform painting + * and receive input events for this {@code JLayer}. + * + * @param ui the {@link javax.swing.plaf.LayerUI} for this {@code JLayer} + */ + public void setUI(LayerUI ui) { + this.layerUI = ui; + super.setUI(ui); + } + + /** + * Returns the {@link javax.swing.plaf.LayerUI} for this {@code JLayer}. + * + * @return the {@code LayerUI} for this {@code JLayer} + */ + public LayerUI getUI() { + return layerUI; + } + + /** + * Returns the {@code JLayer}'s glassPane component or {@code null}. + *
This is a bound property. + * + * @return the {@code JLayer}'s glassPane component + * or {@code null} if none exists + * + * @see #setGlassPane(JPanel) + */ + public JPanel getGlassPane() { + return glassPane; + } + + /** + * Sets the {@code JLayer}'s glassPane component, which can be {@code null}. + *
This is a bound property. + * + * @param glassPane the glassPane component of this {@code JLayer} + * + * @see #getGlassPane() + */ + public void setGlassPane(JPanel glassPane) { + Component oldGlassPane = getGlassPane(); + if (oldGlassPane != null) { + super.remove(oldGlassPane); + } + if (glassPane != null) { + super.addImpl(glassPane, null, 0); + } + this.glassPane = glassPane; + firePropertyChange("glassPane", oldGlassPane, glassPane); + revalidate(); + repaint(); + } + + /** + * Called by the constructor methods to create a default {@code glassPane}. + * By default this method creates a new JPanel with visibility set to true + * and opacity set to false. + * + * @return the default {@code glassPane} + */ + public JPanel createGlassPane() { + return new DefaultLayerGlassPane(); + } + + /** + * This method is not supported by {@code JLayer} + * and always throws {@code UnsupportedOperationException} + * + * @throws UnsupportedOperationException this method is not supported + * + * @see #setView(Component) + * @see #setGlassPane(Component) + */ + protected void addImpl(Component comp, Object constraints, int index) { + throw new UnsupportedOperationException( + "Adding components to JLayer is not supported, " + + "use setView() or setGlassPane() instead"); + } + + /** + * {@inheritDoc} + */ + public void remove(Component comp) { + if (comp == getView()) { + setView(null); + } else if (comp == getGlassPane()) { + setGlassPane(null); + } else { + super.remove(comp); + } + } + + /** + * {@inheritDoc} + */ + public void removeAll() { + setView(null); + setGlassPane(null); + } + + /** + * Delegates all painting to the {@link javax.swing.plaf.LayerUI} object. + * + * @param g the {@code Graphics} to render to + */ + public void paint(Graphics g) { + if (!isPainting) { + isPainting = true; + super.paintComponent(g); + isPainting = false; + } else { + super.paint(g); + } + } + + /** + * This method is empty, because all painting is done by + * {@link #paint(Graphics)} and + * {@link javax.swing.plaf.LayerUI#update(Graphics, JComponent)} methods + */ + protected void paintComponent(Graphics g) { + } + + /** + * To enable the correct painting of the {@code glassPane} and view component, + * the {@code JLayer} overrides the default implementation of + * this method to return {@code false} when the {@code glassPane} is visible. + * + * @return false if {@code JLayer}'s {@code glassPane} is visible + */ + public boolean isOptimizedDrawingEnabled() { + return !glassPane.isVisible(); + } + + /** + * {@inheritDoc} + */ + public void propertyChange(PropertyChangeEvent evt) { + if (getUI() != null) { + getUI().applyPropertyChange(evt, this); + } + } + + /** + * Sets the bitmask of event types to receive by this {@code JLayer}. + * Here is the list of the supported event types: + *
    + *
  • AWTEvent.COMPONENT_EVENT_MASK
  • + *
  • AWTEvent.CONTAINER_EVENT_MASK
  • + *
  • AWTEvent.FOCUS_EVENT_MASK
  • + *
  • AWTEvent.KEY_EVENT_MASK
  • + *
  • AWTEvent.MOUSE_WHEEL_EVENT_MASK
  • + *
  • AWTEvent.MOUSE_MOTION_EVENT_MASK
  • + *
  • AWTEvent.MOUSE_EVENT_MASK
  • + *
  • AWTEvent.INPUT_METHOD_EVENT_MASK
  • + *
  • AWTEvent.HIERARCHY_EVENT_MASK
  • + *
  • AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK
  • + *
+ *

+ * If {@code LayerUI} is installed, + * {@link javax.swing.plaf.LayerUI#eventDispatched(AWTEvent, JLayer)} method + * will only receive events that match the event mask. + *

+ * The following example shows how to correclty use this method + * in the {@code LayerUI} implementations: + *

+     *    public void installUI(JComponent c) {
+     *       super.installUI(c);
+     *       JLayer l = (JLayer) c;
+     *       // this LayerUI will receive only key and focus events
+     *       l.setLayerEventMask(AWTEvent.KEY_EVENT_MASK | AWTEvent.FOCUS_EVENT_MASK);
+     *    }
+     *
+     *    public void uninstallUI(JComponent c) {
+     *       super.uninstallUI(c);
+     *       JLayer l = (JLayer) c;
+     *       // JLayer must be returned to its initial state
+     *       l.setLayerEventMask(0);
+     *    }
+     * 
+ * + * By default {@code JLayer} receives no events. + * + * @param layerEventMask the bitmask of event types to receive + * + * @throws IllegalArgumentException if the {@code layerEventMask} parameter + * contains unsupported event types + * @see #getLayerEventMask() + */ + public void setLayerEventMask(long layerEventMask) { + if (layerEventMask != (layerEventMask & ACCEPTED_EVENTS)) { + throw new IllegalArgumentException( + "The event bitmask contains unsupported event types"); + } + long oldEventMask = getLayerEventMask(); + this.eventMask = layerEventMask; + firePropertyChange("layerEventMask", oldEventMask, layerEventMask); + if (layerEventMask != oldEventMask) { + disableEvents(oldEventMask); + enableEvents(eventMask); + eventController.updateAWTEventListener(this); + } + } + + /** + * Returns the bitmap of event mask to receive by this {@code JLayer} + * and its {@code LayerUI}. + *

+ * It means that {@link javax.swing.plaf.LayerUI#eventDispatched(AWTEvent, JLayer)} method + * will only receive events that match the event mask. + *

+ * By default {@code JLayer} receives no events. + * + * @return the bitmask of event types to receive for this {@code JLayer} + */ + public long getLayerEventMask() { + return eventMask; + } + + /** + * Delegates its functionality to the {@link javax.swing.plaf.LayerUI#updateUI(JLayer)} method, + * if {@code LayerUI} is set. + */ + public void updateUI() { + if (getUI() != null) { + getUI().updateUI(this); + } + } + + /** + * Returns the preferred size of the viewport for a view component. + *

+ * If the ui delegate of this layer is not {@code null}, this method delegates its + * implementation to the {@code LayerUI.getPreferredScrollableViewportSize(JLayer)} + * + * @return the preferred size of the viewport for a view component + * + * @see Scrollable + * @see LayerUI#getPreferredScrollableViewportSize(JLayer) + */ + public Dimension getPreferredScrollableViewportSize() { + if (getUI() != null) { + return getUI().getPreferredScrollableViewportSize(this); + } + return getPreferredSize(); + } + + /** + * Returns a scroll increment, which is required for components + * that display logical rows or columns in order to completely expose + * one block of rows or columns, depending on the value of orientation. + *

+ * If the ui delegate of this layer is not {@code null}, this method delegates its + * implementation to the {@code LayerUI.getScrollableBlockIncrement(JLayer,Rectangle,int,int)} + * + * @return the "block" increment for scrolling in the specified direction + * + * @see Scrollable + * @see LayerUI#getScrollableBlockIncrement(JLayer, Rectangle, int, int) + */ + public int getScrollableBlockIncrement(Rectangle visibleRect, + int orientation, int direction) { + if (getUI() != null) { + return getUI().getScrollableBlockIncrement(this, visibleRect, + orientation, direction); + } + return (orientation == SwingConstants.VERTICAL) ? visibleRect.height : + visibleRect.width; + } + + /** + * Returns {@code false} to indicate that the height of the viewport does not + * determine the height of the layer, unless the preferred height + * of the layer is smaller than the height of the viewport. + *

+ * If the ui delegate of this layer is not null, this method delegates its + * implementation to the {@code LayerUI.getScrollableTracksViewportHeight(JLayer)} + * + * @return whether the layer should track the height of the viewport + * + * @see Scrollable + * @see LayerUI#getScrollableTracksViewportHeight(JLayer) + */ + public boolean getScrollableTracksViewportHeight() { + if (getUI() != null) { + return getUI().getScrollableTracksViewportHeight(this); + } + if (getParent() instanceof JViewport) { + return ((getParent()).getHeight() > getPreferredSize().height); + } + return false; + } + + /** + * Returns {@code false} to indicate that the width of the viewport does not + * determine the width of the layer, unless the preferred width + * of the layer is smaller than the width of the viewport. + *

+ * If the ui delegate of this layer is not null, this method delegates its + * implementation to the {@code LayerUI.getScrollableTracksViewportWidth(JLayer)} + * + * @return whether the layer should track the width of the viewport + * + * @see Scrollable + * @see LayerUI#getScrollableTracksViewportWidth(JLayer) + */ + public boolean getScrollableTracksViewportWidth() { + if (getUI() != null) { + return getUI().getScrollableTracksViewportWidth(this); + } + if (getParent() instanceof JViewport) { + return ((getParent()).getWidth() > getPreferredSize().width); + } + return false; + } + + /** + * Returns a scroll increment, which is required for components + * that display logical rows or columns in order to completely expose + * one new row or column, depending on the value of orientation. + * Ideally, components should handle a partially exposed row or column + * by returning the distance required to completely expose the item. + *

+ * Scrolling containers, like {@code JScrollPane}, will use this method + * each time the user requests a unit scroll. + *

+ * If the ui delegate of this layer is not {@code null}, this method delegates its + * implementation to the {@code LayerUI.getScrollableUnitIncrement(JLayer,Rectangle,int,int)} + * + * @return The "unit" increment for scrolling in the specified direction. + * This value should always be positive. + * + * @see Scrollable + * @see LayerUI#getScrollableUnitIncrement(JLayer, Rectangle, int, int) + */ + public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, + int direction) { + if (getUI() != null) { + return getUI().getScrollableUnitIncrement( + this, visibleRect, orientation, direction); + } + return 1; + } + + private void readObject(ObjectInputStream s) + throws IOException, ClassNotFoundException { + s.defaultReadObject(); + if (getUI() != null) { + setUI(getUI()); + } + if (getLayerEventMask() != 0) { + eventController.updateAWTEventListener(this); + } + } + + /** + * static AWTEventListener to be shared with all AbstractLayerUIs + */ + private static class LayerEventController implements AWTEventListener { + private ArrayList> layerList = + new ArrayList>(); + + private long currentEventMask; + + @SuppressWarnings("unchecked") + public void eventDispatched(AWTEvent event) { + Object source = event.getSource(); + if (source instanceof Component) { + Component component = (Component) source; + while (component != null) { + if (component instanceof JLayer) { + JLayer l = (JLayer) component; + LayerUI ui = l.getUI(); + if (ui != null && + isEventEnabled(l.getLayerEventMask(), + event.getID())) { + ui.eventDispatched(event, l); + } + } + component = component.getParent(); + } + } + } + + private boolean layerListContains(JLayer l) { + for (WeakReference layerWeakReference : layerList) { + if (layerWeakReference.get() == l) { + return true; + } + } + return false; + } + + private void updateAWTEventListener(JLayer layer) { + if (!layerListContains(layer) && layer.getLayerEventMask() != 0) { + layerList.add(new WeakReference(layer)); + } + long combinedMask = 0; + Iterator> it = layerList.iterator(); + while (it.hasNext()) { + WeakReference weakRef = it.next(); + JLayer currLayer = weakRef.get(); + if (currLayer == null) { + it.remove(); + } else { + combinedMask |= currLayer.getLayerEventMask(); + } + } + if (combinedMask == 0) { + removeAWTEventListener(); + layerList.clear(); + } else if (getCurrentEventMask() != combinedMask) { + removeAWTEventListener(); + addAWTEventListener(combinedMask); + } + } + + private long getCurrentEventMask() { + return currentEventMask; + } + + private void addAWTEventListener(final long eventMask) { + AccessController.doPrivileged(new PrivilegedAction() { + public Void run() { + Toolkit.getDefaultToolkit(). + addAWTEventListener(LayerEventController.this, eventMask); + return null; + } + }); + currentEventMask = eventMask; + } + + private void removeAWTEventListener() { + AccessController.doPrivileged(new PrivilegedAction() { + public Void run() { + Toolkit.getDefaultToolkit(). + removeAWTEventListener(LayerEventController.this); + return null; + } + }); + currentEventMask = 0; + } + + private boolean isEventEnabled(long eventMask, int id) { + return (((eventMask & AWTEvent.COMPONENT_EVENT_MASK) != 0 && + id >= ComponentEvent.COMPONENT_FIRST && + id <= ComponentEvent.COMPONENT_LAST) + || ((eventMask & AWTEvent.CONTAINER_EVENT_MASK) != 0 && + id >= ContainerEvent.CONTAINER_FIRST && + id <= ContainerEvent.CONTAINER_LAST) + || ((eventMask & AWTEvent.FOCUS_EVENT_MASK) != 0 && + id >= FocusEvent.FOCUS_FIRST && + id <= FocusEvent.FOCUS_LAST) + || ((eventMask & AWTEvent.KEY_EVENT_MASK) != 0 && + id >= KeyEvent.KEY_FIRST && + id <= KeyEvent.KEY_LAST) + || ((eventMask & AWTEvent.MOUSE_WHEEL_EVENT_MASK) != 0 && + id == MouseEvent.MOUSE_WHEEL) + || ((eventMask & AWTEvent.MOUSE_MOTION_EVENT_MASK) != 0 && + (id == MouseEvent.MOUSE_MOVED || + id == MouseEvent.MOUSE_DRAGGED)) + || ((eventMask & AWTEvent.MOUSE_EVENT_MASK) != 0 && + id != MouseEvent.MOUSE_MOVED && + id != MouseEvent.MOUSE_DRAGGED && + id != MouseEvent.MOUSE_WHEEL && + id >= MouseEvent.MOUSE_FIRST && + id <= MouseEvent.MOUSE_LAST) + || ((eventMask & AWTEvent.INPUT_METHOD_EVENT_MASK) != 0 && + id >= InputMethodEvent.INPUT_METHOD_FIRST && + id <= InputMethodEvent.INPUT_METHOD_LAST) + || ((eventMask & AWTEvent.HIERARCHY_EVENT_MASK) != 0 && + id == HierarchyEvent.HIERARCHY_CHANGED) + || ((eventMask & AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK) != 0 && + (id == HierarchyEvent.ANCESTOR_MOVED || + id == HierarchyEvent.ANCESTOR_RESIZED))); + } + } + + /** + * The default glassPane for the {@link javax.swing.JLayer}. + * It is a subclass of {@code JPanel} which is non opaque by default. + */ + private static class DefaultLayerGlassPane extends JPanel { + /** + * Creates a new {@link DefaultLayerGlassPane} + */ + public DefaultLayerGlassPane() { + setOpaque(false); + } + + /** + * First, implementatation of this method iterates through + * glassPane's child components and returns {@code true} + * if any of them is visible and contains passed x,y point. + * After that it checks if no mouseListeners is attached to this component + * and no mouse cursor is set, then it returns {@code false}, + * otherwise calls the super implementation of this method. + * + * @param x the x coordinate of the point + * @param y the y coordinate of the point + * @return true if this component logically contains x,y + */ + public boolean contains(int x, int y) { + for (int i = 0; i < getComponentCount(); i++) { + Component c = getComponent(i); + Point point = SwingUtilities.convertPoint(this, new Point(x, y), c); + if(c.isVisible() && c.contains(point)){ + return true; + } + } + if (getMouseListeners().length == 0 + && getMouseMotionListeners().length == 0 + && getMouseWheelListeners().length == 0 + && !isCursorSet()) { + return false; + } + return super.contains(x, y); + } + } + + /** + * The default layout manager for the {@link javax.swing.JLayer}.
+ * It places the glassPane on top of the view component + * and makes it the same size as {@code JLayer}, + * it also makes the view component the same size but minus layer's insets
+ */ + private static class DefaultLayerLayout implements LayoutManager, Serializable { + /** + * {@inheritDoc} + */ + public void layoutContainer(Container parent) { + JLayer layer = (JLayer) parent; + Component view = layer.getView(); + Component glassPane = layer.getGlassPane(); + if (view != null) { + Insets insets = layer.getInsets(); + view.setLocation(insets.left, insets.top); + view.setSize(layer.getWidth() - insets.left - insets.right, + layer.getHeight() - insets.top - insets.bottom); + } + if (glassPane != null) { + glassPane.setLocation(0, 0); + glassPane.setSize(layer.getWidth(), layer.getHeight()); + } + } + + /** + * {@inheritDoc} + */ + public Dimension minimumLayoutSize(Container parent) { + JLayer layer = (JLayer) parent; + Insets insets = layer.getInsets(); + Dimension ret = new Dimension(insets.left + insets.right, + insets.top + insets.bottom); + Component view = layer.getView(); + if (view != null) { + Dimension size = view.getMinimumSize(); + ret.width += size.width; + ret.height += size.height; + } + if (ret.width == 0 || ret.height == 0) { + ret.width = ret.height = 4; + } + return ret; + } + + /** + * {@inheritDoc} + */ + public Dimension preferredLayoutSize(Container parent) { + JLayer layer = (JLayer) parent; + Insets insets = layer.getInsets(); + Dimension ret = new Dimension(insets.left + insets.right, + insets.top + insets.bottom); + Component view = layer.getView(); + if (view != null) { + Dimension size = view.getPreferredSize(); + if (size.width > 0 && size.height > 0) { + ret.width += size.width; + ret.height += size.height; + } + } + return ret; + } + + /** + * {@inheritDoc} + */ + public void addLayoutComponent(String name, Component comp) { + } + + /** + * {@inheritDoc} + */ + public void removeLayoutComponent(Component comp) { + } + } +} \ No newline at end of file diff --git a/src/share/classes/javax/swing/plaf/LayerUI.java b/src/share/classes/javax/swing/plaf/LayerUI.java new file mode 100644 index 0000000000000000000000000000000000000000..44c57ce3cad0ec6fca7c6f0d00ab1d243a81c202 --- /dev/null +++ b/src/share/classes/javax/swing/plaf/LayerUI.java @@ -0,0 +1,370 @@ +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ + +package javax.swing.plaf; + +import javax.accessibility.Accessible; +import javax.swing.*; +import javax.swing.plaf.ComponentUI; +import java.awt.*; +import java.awt.event.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeSupport; +import java.beans.PropertyChangeListener; +import java.io.Serializable; + +/** + * The base class for all {@link javax.swing.JLayer}'s UI delegates. + *

+ * {@link #paint(java.awt.Graphics, javax.swing.JComponent)} method performes the + * painting of the {@code JLayer} + * and {@link #eventDispatched(AWTEvent, JLayer)} method is notified + * about any {@code AWTEvent}s which have been generated by a {@code JLayer} + * or any of its subcomponents. + *

+ * The {@code LayerUI} differs from the UI delegates of the other components, + * because it is LookAndFeel independent and is not updated by default when + * the system LookAndFeel is changed. + *

+ * The subclasses of {@code LayerUI} can either be stateless and shareable + * by multiple {@code JLayer}s or not shareable. + * + * @param one of the super types of {@code JLayer}'s view component + * + * @see JLayer#setUI(LayerUI) + * @see JLayer#setView(Component) + * @see JLayer#getView() + * @since 1.7 + * + * @author Alexander Potochkin + */ +public class LayerUI + extends ComponentUI implements Serializable { + + private final PropertyChangeSupport propertyChangeSupport = + new PropertyChangeSupport(this); + + /** + * Paints the specified component. + * Subclasses should override this method and use + * the specified {@code Graphics} object to + * render the content of the component. + * + * @param g the {@code Graphics} context in which to paint; + * @param c the component being painted; + * it can be safely cast to the {@code JLayer} + */ + @Override + public void paint(Graphics g, JComponent c) { + c.paint(g); + } + + /** + * Dispatches {@code AWTEvent}s for {@code JLayer} + * and all it subcomponents to this {@code LayerUI} instance. + *

+ * To enable the {@code AWTEvent} of the particular type, + * you call {@link javax.swing.JLayer#setLayerEventMask} + * in {@link #installUI(javax.swing.JComponent)} + * and set the layer event mask to {@code 0} + * in {@link #uninstallUI(javax.swing.JComponent)} after that + * + * @param e the event to be dispatched + * @param l the layer this LayerUI is set to + * + * @see JLayer#setLayerEventMask(long) + * @see javax.swing.JLayer#getLayerEventMask() + */ + public void eventDispatched(AWTEvent e, JLayer l){ + } + + /** + * Invoked when {@link javax.swing.JLayer#updateUI()} is called + * by the {@code JLayer} this {@code LayerUI} is set to. + * + * @param l the {@code JLayer} which UI is updated + */ + public void updateUI(JLayer l){ + } + + /** + * Configures the {@code JLayer} this {@code LayerUI} is set to. + * The default implementation registers the {@code LayerUI} + * as a property change listener for the passed {@code JLayer} component. + * + * @param c the {@code JLayer} component where this UI delegate is being installed + */ + public void installUI(JComponent c) { + addPropertyChangeListener((JLayer) c); + } + + /** + * Reverses the configuration which was previously set + * in the {@link #installUI(JComponent)} method. + * The default implementation unregisters the property change listener + * for the passed JLayer component. + * + * @param c the component from which this UI delegate is being removed. + */ + public void uninstallUI(JComponent c) { + removePropertyChangeListener((JLayer) c); + } + + /** + * Adds a PropertyChangeListener to the listener list. The listener is + * registered for all bound properties of this class. + *

+ * If {@code listener} is {@code null}, + * no exception is thrown and no action is performed. + * + * @param listener the property change listener to be added + * @see #removePropertyChangeListener + * @see #getPropertyChangeListeners + * @see #addPropertyChangeListener(String, java.beans.PropertyChangeListener) + */ + public void addPropertyChangeListener(PropertyChangeListener listener) { + propertyChangeSupport.addPropertyChangeListener(listener); + } + + /** + * Removes a PropertyChangeListener from the listener list. This method + * should be used to remove PropertyChangeListeners that were registered + * for all bound properties of this class. + *

+ * If {@code listener} is {@code null}, + * no exception is thrown and no action is performed. + * + * @param listener the PropertyChangeListener to be removed + * @see #addPropertyChangeListener + * @see #getPropertyChangeListeners + * @see #removePropertyChangeListener(String, PropertyChangeListener) + */ + public void removePropertyChangeListener(PropertyChangeListener listener) { + propertyChangeSupport.removePropertyChangeListener(listener); + } + + /** + * Returns an array of all the property change listeners + * registered on this component. + * + * @return all of this ui's {@code PropertyChangeListener}s + * or an empty array if no property change + * listeners are currently registered + * @see #addPropertyChangeListener + * @see #removePropertyChangeListener + * @see #getPropertyChangeListeners(String) + */ + public PropertyChangeListener[] getPropertyChangeListeners() { + return propertyChangeSupport.getPropertyChangeListeners(); + } + + /** + * Adds a PropertyChangeListener to the listener list for a specific + * property. + *

+ * If {@code propertyName} or {@code listener} is {@code null}, + * no exception is thrown and no action is taken. + * + * @param propertyName one of the property names listed above + * @param listener the property change listener to be added + * @see #removePropertyChangeListener(String, PropertyChangeListener) + * @see #getPropertyChangeListeners(String) + * @see #addPropertyChangeListener(String, PropertyChangeListener) + */ + public void addPropertyChangeListener(String propertyName, + PropertyChangeListener listener) { + propertyChangeSupport.addPropertyChangeListener(propertyName, listener); + } + + /** + * Removes a {@code PropertyChangeListener} from the listener + * list for a specific property. This method should be used to remove + * {@code PropertyChangeListener}s + * that were registered for a specific bound property. + *

+ * If {@code propertyName} or {@code listener} is {@code null}, + * no exception is thrown and no action is taken. + * + * @param propertyName a valid property name + * @param listener the PropertyChangeListener to be removed + * @see #addPropertyChangeListener(String, PropertyChangeListener) + * @see #getPropertyChangeListeners(String) + * @see #removePropertyChangeListener(PropertyChangeListener) + */ + public void removePropertyChangeListener(String propertyName, + PropertyChangeListener listener) { + propertyChangeSupport.removePropertyChangeListener(propertyName, listener); + } + + /** + * Returns an array of all the listeners which have been associated + * with the named property. + * + * @return all of the {@code PropertyChangeListener}s associated with + * the named property; if no such listeners have been added or + * if {@code propertyName} is {@code null}, an empty + * array is returned + * @see #addPropertyChangeListener(String, PropertyChangeListener) + * @see #removePropertyChangeListener(String, PropertyChangeListener) + * @see #getPropertyChangeListeners + */ + public PropertyChangeListener[] getPropertyChangeListeners(String propertyName) { + return propertyChangeSupport.getPropertyChangeListeners(propertyName); + } + + /** + * Support for reporting bound property changes for Object properties. + * This method can be called when a bound property has changed and it will + * send the appropriate PropertyChangeEvent to any registered + * PropertyChangeListeners. + * + * @param propertyName the property whose value has changed + * @param oldValue the property's previous value + * @param newValue the property's new value + */ + protected void firePropertyChange(String propertyName, + Object oldValue, Object newValue) { + propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue); + } + + /** + * Notifies the {@code LayerUI} when any of its property are changed + * and enables updating every {@code JLayer} this {@code LayerUI} instance is set to. + * + * @param evt the PropertyChangeEvent generated by this {@code LayerUI} + * @param l the {@code JLayer} this LayerUI is set to + */ + public void applyPropertyChange(PropertyChangeEvent evt, JLayer l) { + } + + /** + * Returns the preferred size of the viewport for a view component. + * + * @return the preferred size of the viewport for a view component + * @see Scrollable#getPreferredScrollableViewportSize() + */ + public Dimension getPreferredScrollableViewportSize(JLayer l) { + if (l.getView() instanceof Scrollable) { + return ((Scrollable)l.getView()).getPreferredScrollableViewportSize(); + } + return l.getPreferredSize(); + } + + /** + * Returns a scroll increment, which is required for components + * that display logical rows or columns in order to completely expose + * one block of rows or columns, depending on the value of orientation. + * + * @return the "block" increment for scrolling in the specified direction + * @see Scrollable#getScrollableBlockIncrement(Rectangle, int, int) + */ + public int getScrollableBlockIncrement(JLayer l, + Rectangle visibleRect, + int orientation, int direction) { + if (l.getView() instanceof Scrollable) { + return ((Scrollable)l.getView()).getScrollableBlockIncrement( + visibleRect,orientation, direction); + } + return (orientation == SwingConstants.VERTICAL) ? visibleRect.height : + visibleRect.width; + } + + /** + * Returns {@code false} to indicate that the height of the viewport does not + * determine the height of the layer, unless the preferred height + * of the layer is smaller than the height of the viewport. + * + * @return whether the layer should track the height of the viewport + * @see Scrollable#getScrollableTracksViewportHeight() + */ + public boolean getScrollableTracksViewportHeight(JLayer l) { + if (l.getView() instanceof Scrollable) { + return ((Scrollable)l.getView()).getScrollableTracksViewportHeight(); + } + if (l.getParent() instanceof JViewport) { + return (((JViewport)l.getParent()).getHeight() > l.getPreferredSize().height); + } + return false; + } + + /** + * Returns {@code false} to indicate that the width of the viewport does not + * determine the width of the layer, unless the preferred width + * of the layer is smaller than the width of the viewport. + * + * @return whether the layer should track the width of the viewport + * @see Scrollable + * @see LayerUI#getScrollableTracksViewportWidth(JLayer) + */ + public boolean getScrollableTracksViewportWidth(JLayer l) { + if (l.getView() instanceof Scrollable) { + return ((Scrollable)l.getView()).getScrollableTracksViewportWidth(); + } + if (l.getParent() instanceof JViewport) { + return (((JViewport)l.getParent()).getWidth() > l.getPreferredSize().width); + } + return false; + } + + /** + * Returns a scroll increment, which is required for components + * that display logical rows or columns in order to completely expose + * one new row or column, depending on the value of orientation. + * Ideally, components should handle a partially exposed row or column + * by returning the distance required to completely expose the item. + *

+ * Scrolling containers, like JScrollPane, will use this method + * each time the user requests a unit scroll. + * + * @return The "unit" increment for scrolling in the specified direction. + * This value should always be positive. + * @see Scrollable#getScrollableUnitIncrement(Rectangle, int, int) + */ + public int getScrollableUnitIncrement(JLayer l, + Rectangle visibleRect, + int orientation, int direction) { + if (l.getView() instanceof Scrollable) { + return ((Scrollable)l.getView()).getScrollableUnitIncrement( + visibleRect, orientation, direction); + } + return 1; + } + + /** + * If the {@code JLayer}'s view component is not {@code null}, + * this calls the view's {@code getBaseline()} method. + * Otherwise, the default implementation is called. + * + * @param c {@code JLayer} to return baseline resize behavior for + * @param width the width to get the baseline for + * @param height the height to get the baseline for + * @return baseline or a value < 0 indicating there is no reasonable + * baseline + */ + public int getBaseline(JComponent c, int width, int height) { + JLayer l = (JLayer) c; + if (l.getView() != null) { + return l.getView().getBaseline(width, height); + } + return super.getBaseline(c, width, height); + } + + /** + * If the {@code JLayer}'s view component is not {@code null}, + * this calls the view's {@code getBaselineResizeBehavior()} method. + * Otherwise, the default implementation is called. + * + * @param c {@code JLayer} to return baseline resize behavior for + * @return an enum indicating how the baseline changes as the component + * size changes + */ + public Component.BaselineResizeBehavior getBaselineResizeBehavior(JComponent c) { + JLayer l = (JLayer) c; + if (l.getView() != null) { + return l.getView().getBaselineResizeBehavior(); + } + return super.getBaselineResizeBehavior(c); + } +} \ No newline at end of file diff --git a/test/javax/swing/JLayer/SerializationTest/SerializationTest.java b/test/javax/swing/JLayer/SerializationTest/SerializationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..0c18e41491e1f84543bdebf7ddb5373261957b3f --- /dev/null +++ b/test/javax/swing/JLayer/SerializationTest/SerializationTest.java @@ -0,0 +1,53 @@ +/* + * @test + * @summary Makes sure that JLayer is synchronizable + * @author Alexander Potochkin + * @run main SerializationTest + */ + +import javax.swing.*; +import javax.swing.plaf.LayerUI; +import java.io.ByteArrayInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectInputStream; +import java.io.ByteArrayOutputStream; + +public class SerializationTest { + + public static void main(String[] args) throws Exception { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream); + + JLayer layer = new JLayer(new JButton("Hello")); + + layer.setUI(new TestLayerUI()); + + outputStream.writeObject(layer); + outputStream.flush(); + + ByteArrayInputStream byteArrayInputStream = + new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); + ObjectInputStream inputStream = new ObjectInputStream(byteArrayInputStream); + + JLayer newLayer = (JLayer) inputStream.readObject(); + + if (newLayer.getLayout() == null) { + throw new RuntimeException("JLayer's layout is null"); + } + if (newLayer.getGlassPane() == null) { + throw new RuntimeException("JLayer's glassPane is null"); + } + if (newLayer.getUI().getClass() != layer.getUI().getClass()) { + throw new RuntimeException("Different UIs"); + } + if (newLayer.getView().getClass() != layer.getView().getClass()) { + throw new RuntimeException("Different Views"); + } + } + + static class TestLayerUI extends LayerUI { + public String toString() { + return "TestLayerUI"; + } + } +} \ No newline at end of file