All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jdesktop.jxlayer.JXLayer Maven / Gradle / Ivy

There is a newer version: 3.0.4
Show newest version
/**
 * Copyright (c) 2006-2008, Alexander Potochkin
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the JXLayer project nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.jdesktop.jxlayer;

import org.jdesktop.jxlayer.plaf.AbstractLayerUI;
import org.jdesktop.jxlayer.plaf.LayerUI;
import org.jdesktop.jxlayer.plaf.item.LayerItemChangeEvent;
import org.jdesktop.jxlayer.plaf.item.LayerItemListener;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.im.InputContext;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Locale;

/**
 * The universal decorator for Swing components
 * with which you can implement various advanced painting effects
 * as well as receive notification of all {@code MouseEvent}s,
 * {@code KeyEvent}s and {@code FocusEvent}s which generated within its borders.
 * 

* {@code JXLayer} delegates its painting and input events handling * to its {@link LayerUI} object which performs the actual decoration. *

* The custom painting and events notification automatically work * for {@code JXLayer} itself and all its subcomponents. * This powerful combination makes it possible to enrich existing components * with new advanced functionality such as temporary locking of a hierarchy, * data tips for compound components, enhanced mouse scrolling etc... *

* {@code JXLayer} is a great solution if you just need to do custom painting * over compound component or catch input events of 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 * AbstractLayerUI<JPanel> layerUI = new AbstractLayerUI<JPanel>() { *

* protected void paintLayer(Graphics2D g2, JXLayer<JPanel> l) { * // this paints the layer as is * super.paintLayer(g2, l); * // fill it with the translucent green * g2.setColor(new Color(0, 128, 0, 128)); * g2.fillRect(0, 0, l.getWidth(), l.getHeight()); * } *

* // overridden method which catches MouseMotion events * protected void processMouseMotionEvent(MouseEvent e) { * System.out.println("MouseMotionEvent detected: " * + e.getX() + " " + e.getY()); * } * }; *

* // create the layer for the panel using our custom layerUI * JXLayer<JPanel> layer = new JXLayer<JPanel>(panel, layerUI); *

* // work with the layer as with any other Swing component * frame.add(layer); *

*

* Note: When a {@code LayerUI} instance is disabled or not set, * its {@code JXLayer}s temporary lose all their decorations. * Note: {@code JXLayer} is very friendly to your application, * it uses only public Swing API and doesn't rely on any global settings * like custom {@code RepaintManager} or {@code AWTEventListener}. * It neither change the opaque state of its subcomponents * nor use the glassPane of its parent frame. *

* {@code JXLayer} can be used under restricted environment * (e.g. unsigned applets) * * @see #setUI(LayerUI) * @see LayerUI * @see AbstractLayerUI */ public final class JXLayer extends JComponent implements Scrollable, LayerItemListener, PropertyChangeListener { private V view; private JComponent glassPane; private boolean isPainting; private static final LayerLayout sharedLayoutInstance = new LayerLayout(); private final InputContext inputContext = new LayerInputContext(); private boolean isProxyInputContextEnabled = true; /** * Creates a new {@code JXLayer} object with empty view component * and empty {@link LayerUI}. * * @see #setView * @see #setUI */ public JXLayer() { this(null); } /** * Creates a new {@code JXLayer} object with empty {@link LayerUI}. * * @param view the component to be decorated with this {@code JXLayer} * @see #setUI */ public JXLayer(V view) { this(view, null); } /** * Creates a new {@code JXLayer} object with provided view component * and {@link LayerUI} object. * * @param view the component to be decorated * @param ui the {@link LayerUI} deleagate to be used by this {@code JXLayer} */ public JXLayer(V view, LayerUI ui) { setLayout(sharedLayoutInstance); setGlassPane(new JXGlassPane()); setView(view); setUI(ui); } /** * Returns the view component for this {@code JXLayer}. *
This is a bound property. * * @return the view component for this {@code JXLayer} */ public V getView() { return view; } /** * Sets the view component (the component to be decorated) * for this {@code JXLayer}.
This is a bound property. * * @param view the view component for this {@code JXLayer} */ public void setView(V view) { JComponent 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 LayerUI} which will perform painting * and receive input events for this {@code JXLayer}. * * @param ui the {@link LayerUI} for this {@code JXLayer} */ public void setUI(LayerUI ui) { if (isValidUI()) { disableEvents(getUI().getLayerEventMask()); } super.setUI(ui); if (isValidUI()) { getGlassPane().setVisible(true); enableEvents(ui.getLayerEventMask()); } else { getGlassPane().setVisible(false); } } /** * Returns the {@link LayerUI} for this {@code JXLayer}. * * @return the {@link LayerUI} for this {@code JXLayer} */ @SuppressWarnings("unchecked") public LayerUI getUI() { return (LayerUI) ui; } /** * Returns the glassPane component of this {@code JXLayer}. *
This is a bound property. * * @return the glassPane component of this {@code JXLayer} */ public JComponent getGlassPane() { return glassPane; } /** * Sets the glassPane component of this {@code JXLayer}. *
This is a bound property. * * @param glassPane the glassPane component of this {@code JXLayer} */ public void setGlassPane(JComponent glassPane) { if (glassPane == null) { throw new IllegalArgumentException("GlassPane can't be set to null"); } JComponent oldGlassPane = getGlassPane(); if (oldGlassPane != null) { super.remove(oldGlassPane); } super.addImpl(glassPane, null, 0); this.glassPane = glassPane; firePropertyChange("glassPane", oldGlassPane, glassPane); revalidate(); repaint(); } /** * {@code JXLayer} can have only two direct children: * the view component and the glassPane, * so this method throws {@code UnsupportedOperationException}. *

* {@inheritDoc} * * @see #setView * @see #setGlassPane */ protected void addImpl(Component comp, Object constraints, int index) { if (comp instanceof JComponent) { setView((V) comp); } else { throw new IllegalArgumentException("Component is not instance of JComponent"); } } /** * Removes the {@code JXLayer}'s view component. * * @param comp the component to be removed */ public void remove(Component comp) { if (comp == getView()) { view = null; } else if (comp == getGlassPane()) { throw new IllegalArgumentException("GlassPane can't be removed"); } super.remove(comp); } /** * Removes the {@code JXLayer}'s view component. */ public void removeAll() { setView(null); } private boolean isValidUI() { return getUI() != null && getUI().isEnabled(); } /** * Delegates all painting to the {@link LayerUI} object. *

* If no view component or {@code LayerUI} object is provided, * {@link LayerUI#isEnabled()} returns {@code false}, * any of {@code JXLayer}'s size is less than {@code 1} * or {@code g} is not instance of Graphics2D * then the super implementation of {@code paint} method is called. * * @param g the {@code Graphics} to render to */ public void paint(Graphics g) { LayerUI layerUI = getUI(); if (!isPainting && g instanceof Graphics2D && isValidUI() && getWidth() > 0 && getHeight() > 0) { Graphics2D g2 = (Graphics2D) g.create(); isPainting = true; layerUI.paint(g2, this); isPainting = false; g2.dispose(); } else { super.paint(g); } } /** * To enable the correct painting of the glassPane and view component, * the {@code JXLayer} overrides the default implementation of * this method to return {@code false} when the glassPane is visible. * * @return false */ public boolean isOptimizedDrawingEnabled() { return !glassPane.isVisible(); } /** * This method is public as an implementation side effect. * {@code JXLayer} can be registered as a {@code LayerItemListener} * and usually receives the {@code LayerItemChangeEvent}s * from its {@code LayerUI}. * * @param e the {@link LayerItemChangeEvent} */ public void layerItemChanged(LayerItemChangeEvent e) { Shape clip = e.getClip(getWidth(), getHeight()); if (clip != null) { repaint(clip.getBounds()); } else { repaint(); } } /** * {@inheritDoc} */ public void propertyChange(PropertyChangeEvent evt) { String propertyName = evt.getPropertyName(); if ("enabled".equals(propertyName)) { if (getUI().isEnabled()) { getGlassPane().setVisible(true); enableEvents(getUI().getLayerEventMask()); } else { getGlassPane().setVisible(false); disableEvents(getUI().getLayerEventMask()); } } else if ("layerEventMask".equals(propertyName)) { if (getUI().isEnabled()) { disableEvents((Long) evt.getOldValue()); enableEvents(getUI().getLayerEventMask()); } } } /** * Delegates its functionality to the {@link LayerUI#updateUI(JXLayer)} method, * if {@code LayerUI} is set and enabled. */ public void updateUI() { if (isValidUI()) { getUI().updateUI(this); } } /** * Delegates its functionality to the {@link LayerUI#contains(JComponent, int, int)} method, * if {@code LayerUI} is set and enabled. *

* {@inheritDoc} */ public boolean contains(int x, int y) { if (isValidUI()) { return getUI().contains(this, x, y); } // the implementation of Component.inside(int, int) method // which is eventually called by super.contains(int, int) // inside() is deprecated, so I copied this line here return (x >= 0) && (x < getWidth()) && (y >= 0) && (y < getHeight()); } /** * Returns the preferred size of the viewport for a view component. *

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

* If the ui delegate of this layer is not null, this method delegates its * implementation to the {@code LayerUI.getScrollableBlockIncrement(JXLayer,Rectangle,int,int)} * * @return the "block" increment for scrolling in the specified direction * @see Scrollable * @see LayerUI#getScrollableBlockIncrement(JXLayer, 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 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 viewports height. *

* If the ui delegate of this layer is not null, this method delegates its * implementation to the {@code LayerUI.getScrollableTracksViewportHeight(JXLayer)} * * @return whether the layer should track the height of the viewport * @see Scrollable * @see LayerUI#getScrollableTracksViewportHeight(JXLayer) */ public boolean getScrollableTracksViewportHeight() { if (getUI() != null) { return getUI().getScrollableTracksViewportHeight(this); } if (getParent() instanceof JViewport) { return ((getParent()).getHeight() > getPreferredSize().height); } return false; } /** * Returns 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 viewports width. *

* If the ui delegate of this layer is not null, this method delegates its * implementation to the {@code LayerUI.getScrollableTracksViewportWidth(JXLayer)} * * @return whether the layer should track the width of the viewport * @see Scrollable * @see LayerUI#getScrollableTracksViewportWidth(JXLayer) */ public boolean getScrollableTracksViewportWidth() { if (getUI() != null) { return getUI().getScrollableTracksViewportWidth(this); } if (getParent() instanceof JViewport) { return ((getParent()).getWidth() > getPreferredSize().width); } return false; } /** * Components that display logical rows or columns should compute * the scroll increment that will 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. *

* If the ui delegate of this layer is not null, this method delegates its * implementation to the {@code LayerUI.getScrollableUnitIncrement(JXLayer,Rectangle,int,int)} * * @return The "unit" increment for scrolling in the specified direction. * This value should always be positive. * @see Scrollable * @see LayerUI#getScrollableUnitIncrement(JXLayer, Rectangle, int, int) */ public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { if (getUI() != null) { return getUI().getScrollableUnitIncrement( this, visibleRect, orientation, direction); } return 1; } /** * Returns {@code true} is the proxy InputContext is enabled for this JXLayer. *

* When the proxy InputContext is enabled, the JXLayer will notify its LayerUI * about keyboard, mouse & focus events that are generated for this JXLayer * or any of its subcomponent *

* The default value for this property is {@code true} *

* For more informaiton please see * JXLayer 3.0 - Event handling * * @return {@code true} is the proxy InputContext is enabled for this JXLayer * @see LayerUI#eventDispatched(AWTEvent, JXLayer) */ public boolean isProxyInputContextEnabled() { return isProxyInputContextEnabled; } /** * Sets if the the proxy InputContext is enabled for this JXLayer *

* If the proxy InputContext is enabled, the JXLayer will notify its LayerUI * about keyboard, mouse & focus events that are generated for this JXLayer * or any of its subcomponent *

* A LayerUI subclass may implement a different way of catching events, * via ordinary listeners or global {@link AWTEventListener} *

* The default value for this property is {@code true} *

* For more informaiton please see * JXLayer 3.0 - Event handling * * @param isEnabled {@code true} if the proxy InputContext is enabled, * otherwise {@code false} * @see LayerUI#eventDispatched(AWTEvent, JXLayer) */ public void setProxyInputContextEnabled(boolean isEnabled) { isProxyInputContextEnabled = isEnabled; } /** * Returns the proxy input context which is used to catch all input events * and focus events from the subcomponents of this {@code JXLayer}. *

* When input event is happened and {@code LayerUI} is set and enabled * then this proxy input context notifies * the {@link LayerUI#eventDispatched(AWTEvent, JXLayer)} method * and then calls the super implementation. * * @return the private proxy {@code InputContext} instance */ public InputContext getInputContext() { if (isProxyInputContextEnabled()) { return super.getInputContext() == null ? null : inputContext; } return super.getInputContext(); } private class LayerInputContext extends InputContext { public void dispatchEvent(AWTEvent event) { if (isValidUI() && getUI().isEventEnabled(event.getID()) && (event instanceof InputEvent || event instanceof FocusEvent)) { getUI().eventDispatched(event, JXLayer.this); } JXLayer.super.getInputContext().dispatchEvent(event); } public void dispose() { JXLayer.super.getInputContext().dispose(); } public void endComposition() { JXLayer.super.getInputContext().endComposition(); } public Object getInputMethodControlObject() { return JXLayer.super.getInputContext().getInputMethodControlObject(); } public Locale getLocale() { return JXLayer.super.getInputContext().getLocale(); } public boolean isCompositionEnabled() { return JXLayer.super.getInputContext().isCompositionEnabled(); } public void reconvert() { JXLayer.super.getInputContext().reconvert(); } public void removeNotify(Component client) { JXLayer.super.getInputContext().removeNotify(client); } public boolean selectInputMethod(Locale locale) { return JXLayer.super.getInputContext().selectInputMethod(locale); } public void setCharacterSubsets(Character.Subset[] subsets) { JXLayer.super.getInputContext().setCharacterSubsets(subsets); } public void setCompositionEnabled(boolean enable) { JXLayer.super.getInputContext().setCompositionEnabled(enable); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy