001    /**
002     * Copyright (c) 2008-2009, Piet Blok
003     * All rights reserved.
004     *
005     * Redistribution and use in source and binary forms, with or without
006     * modification, are permitted provided that the following conditions
007     * are met:
008     *
009     *   * Redistributions of source code must retain the above copyright
010     *     notice, this list of conditions and the following disclaimer.
011     *   * Redistributions in binary form must reproduce the above
012     *     copyright notice, this list of conditions and the following
013     *     disclaimer in the documentation and/or other materials provided
014     *     with the distribution.
015     *   * Neither the name of the copyright holder nor the names of the
016     *     contributors may be used to endorse or promote products derived
017     *     from this software without specific prior written permission.
018     *
019     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
020     * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
021     * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
022     * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
023     * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
024     * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
025     * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
026     * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
027     * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
028     * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
029     * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
030     */
031    
032    package org.pbjar.jxlayer.plaf.misc;
033    
034    import java.awt.Component;
035    import java.awt.Point;
036    import java.awt.Toolkit;
037    import java.awt.event.AWTEventListener;
038    import java.awt.event.ActionEvent;
039    import java.awt.event.MouseWheelEvent;
040    import java.util.ArrayList;
041    import java.util.List;
042    
043    import javax.swing.AbstractAction;
044    import javax.swing.Action;
045    import javax.swing.JComponent;
046    import javax.swing.SwingUtilities;
047    
048    import org.jdesktop.jxlayer.JXLayer;
049    import org.jdesktop.jxlayer.plaf.AbstractLayerUI;
050    
051    /**
052     * A generalized implementation of LayerUI.
053     * 
054     * <p>
055     * Some additional functionality:
056     * <ol>
057     * <li>It facilitates the use of a state object for {@code LayerUI}s that are
058     * intended for shared use.</li>
059     * <li>It supplies {@code Action}s that can be used for the controls of a GUI.</li>
060     * <li>It re-dispatches {@code MouseWheelEvent}s to the first component up in
061     * the hierarchy from the originating component that has a {@code
062     * MouseWheelListener} registered.</li>
063     * <li>Via the constructor one can enable {@link AWTEventListener}.</li>
064     * </ol>
065     * </p>
066     * 
067     * @author Piet Blok
068     * 
069     * @param <V>
070     *            JXLayer's view
071     * @param <S>
072     *            A state object
073     */
074    public class GeneralLayerUI<V extends JComponent, S extends Object> extends
075            AbstractLayerUI<V> {
076    
077        private final String stateKey = this.getClass().getName() + ".stateKey";
078    
079        private final boolean enableAWTEventListener;
080    
081        /**
082         * Constructor for an instance with {@link AWTEventListener} disabled.
083         */
084        public GeneralLayerUI() {
085            this(false);
086        }
087    
088        /**
089         * Create an instance.
090         * <p>
091         * Via the enableAWTEventListener argument, {@link AWTEventListener} can be
092         * enabled or disabled.
093         * </p>     *
094         * 
095         * @param enableAWTEventListener
096         *            if {@code true}, {@link AWTEventListener} will be enabled.
097         *            Specifying {@code true} Solves a problem with non editable
098         *            {@code JTextComponent}s.
099         * @throws SecurityException
100         *             when enableAWTEventListener is {@code true} and if a security
101         *             manager exists and its {@code checkPermission} method doesn't
102         *             allow the operation.
103         * @see Toolkit#addAWTEventListener(AWTEventListener, long)
104         */
105        public GeneralLayerUI(boolean enableAWTEventListener) {
106            super();
107            this.enableAWTEventListener = enableAWTEventListener;
108        }
109    
110        /**
111         * Get actions that are applicable to this LayerUI. This implementation
112         * returns an enable/disable action for this LayerUI
113         * 
114         * @return a list of applicable actions
115         */
116        public List<Action> getActions() {
117            ArrayList<Action> actionList = new ArrayList<Action>();
118            actionList.add(new AbstractAction("Enable "
119                    + this.getClass().getSimpleName()) {
120    
121                private static final long serialVersionUID = 1L;
122    
123                {
124                    this.putValue(Action.SELECTED_KEY, GeneralLayerUI.this
125                            .isEnabled());
126                }
127    
128                @Override
129                public void actionPerformed(ActionEvent event) {
130                    GeneralLayerUI.this.setEnabled((Boolean) this
131                            .getValue(Action.SELECTED_KEY));
132                }
133            });
134            return actionList;
135        }
136    
137        /**
138         * Get actions that are applicable to this LayerUI and a specific JXLayer.
139         * This implementation returns an empty list
140         * 
141         * @param layer
142         *            the JXLayer
143         * 
144         * @return a list of applicable actions
145         */
146        public List<Action> getActions(JXLayer<V> layer) {
147            ArrayList<Action> actionList = new ArrayList<Action>();
148            return actionList;
149        }
150    
151        /**
152         * Returns the simple class name.
153         * 
154         * @return a name
155         */
156        public String getName() {
157            return this.getClass().getSimpleName();
158        }
159    
160        /**
161         * Invokes super.installUI. Then invokes createStateObject. User
162         * installation actions may be coded in createStateObject.
163         */
164        @SuppressWarnings("unchecked")
165        @Override
166        public void installUI(JComponent c) {
167            super.installUI(c);
168            JXLayer<V> layer = (JXLayer<V>) c;
169            S stateObject = createStateObject(layer);
170            if (stateObject != null) {
171                layer.putClientProperty(stateKey, stateObject);
172            }
173        }
174    
175        /**
176         * Invokes super.uninstallUI. If a state object is available, the method
177         * {@code GeneralLayerUI#cleanupStateObject(Object)} is invoked to cleanup
178         * the state object.
179         */
180        @Override
181        @SuppressWarnings("unchecked")
182        public void uninstallUI(JComponent c) {
183            super.uninstallUI(c);
184            JXLayer<V> layer = (JXLayer<V>) c;
185            S stateObject = getStateObject(layer);
186            if (stateObject != null) {
187                cleanupStateObject(stateObject);
188                layer.putClientProperty(stateKey, null);
189            }
190        }
191    
192        private Component findWheelListenerComponent(Component target) {
193            if (target == null) {
194                return null;
195            } else if (target.getMouseWheelListeners().length == 0) {
196                return findWheelListenerComponent(target.getParent());
197            } else {
198                return target;
199            }
200        }
201    
202        /**
203         * Cleanup the state object. The default implementation does nothing.
204         * 
205         * @param stateObject
206         *            a state object
207         */
208        protected void cleanupStateObject(S stateObject) {
209    
210        }
211    
212        /**
213         * Create a StateObject specific for this LayerUI and the JXLayer argument.
214         * The default implementation returns {@code null}.
215         * 
216         * @param layer
217         *            the JXLayer
218         * @return a StateObject or {@code null}, if no state is maintained.
219         */
220        protected S createStateObject(JXLayer<V> layer) {
221            return null;
222        }
223    
224        /**
225         * Get the created StateObject specific to the JXLayer argument.
226         * 
227         * @param layer
228         *            the JXLayer
229         * @return the StateObject or {@code null}, if
230         *         {@link #createStateObject(JXLayer)} returned null
231         */
232        @SuppressWarnings("unchecked")
233        protected final S getStateObject(JXLayer<V> layer) {
234            return (S) layer.getClientProperty(stateKey);
235        }
236    
237        /**
238         * Returns the value supplied in {@link #GeneralLayerUI(boolean)}.
239         * 
240         * @throws SecurityException
241         *             when {@code true} is returned and if a security manager
242         *             exists and its {@code checkPermission} method doesn't allow
243         *             the operation.
244         */
245        @Override
246        protected final boolean isAWTEventListenerEnabled() {
247            return this.enableAWTEventListener;
248        }
249    
250        /**
251         * Re-dispatches the event to the first component in the hierarchy that has
252         * a {@code MouseWheelEventListener} registered.
253         */
254        @Override
255        protected void processMouseWheelEvent(MouseWheelEvent event,
256                JXLayer<V> jxlayer) {
257            /*
258             * Only process an event if it is not already consumed. This may be the
259             * case if this LayerUI is contained in a wrapped hierarchy.
260             */
261            if (!event.isConsumed()) {
262                /*
263                 * Since we will create a new event, the argument event must be
264                 * consumed.
265                 */
266                event.consume();
267                /*
268                 * Find a target up in the hierarchy that has
269                 * MouseWheelEventListeners registered.
270                 */
271                Component target = event.getComponent();
272                Component newTarget = findWheelListenerComponent(target);
273                if (newTarget == null) {
274                    newTarget = jxlayer.getParent();
275                }
276                /*
277                 * Convert the location relative to the new target
278                 */
279                Point point = SwingUtilities.convertPoint(event.getComponent(),
280                        event.getPoint(), newTarget);
281                /*
282                 * Create a new event
283                 */
284                MouseWheelEvent newEvent = new MouseWheelEvent(newTarget, //
285                        event.getID(), //
286                        event.getWhen(), //
287                        event.getModifiers(), //
288                        point.x, //
289                        point.y, //
290                        event.getClickCount(), //
291                        event.isPopupTrigger(), //
292                        event.getScrollType(), //
293                        event.getScrollAmount(), //
294                        event.getWheelRotation() //
295                );
296                /*
297                 * Dispatch the new event.
298                 */
299                newTarget.dispatchEvent(newEvent);
300            }
301        }
302    
303    }