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 }