001    /*
002     * Copyright (c) 2005, romain guy (romain.guy@jext.org) and craig wickesser (craig@codecraig.com)
003     * All rights reserved.
004     * 
005     * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
006     * 
007     *     * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
008     *     * 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.
009     *     * Neither the name of the <ORGANIZATION> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
010     * 
011     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
012     * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
013     * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
014     * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
015     * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
016     * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
017     * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
018     * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
019     * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
020     * POSSIBILITY OF SUCH DAMAGE.
021     */
022    
023    package net.java.swingfx.jdraggable;
024    
025    import java.awt.Component;
026    import java.awt.Container;
027    import java.awt.event.ContainerEvent;
028    import java.util.HashSet;
029    import java.util.Set;
030    
031    
032    /**
033     * A default implementation of the {@link DraggableManager} interface.  This
034     * implementation provides the basic functionality to enable {@link java.awt.Component}
035     * 's which implement the {@link Draggable} interface to actually be dragged
036     * when a user selects and drags a component.  Depending on the {@link net.java.swingfx.jdraggable.DragPolicy}
037     * that is set, not all components must implement the <code>Draggable</code> interface.
038     * This implementation only supports one component to be dragged at a time.
039     * <br>
040     * <br>
041     * Another idea for an implementation of {@link DraggableManager} is one which
042     * registers a {@link javax.swing.JLayeredPane} as the "Draggable Container"
043     * such that when a component is dragged, it's depth (or Z-Order) within the
044     * "Draggable Container" is changed such that it is on top of the other components.
045     * 
046     * @author craig
047     * @since v0.1
048     * <br>
049     * $Header: /cvs/swingfx/docs/api/src-html/net/java/swingfx/jdraggable/DefaultDraggableManager.html,v 1.1 2005/06/23 00:24:44 codecraig Exp $
050     */
051    public class DefaultDraggableManager implements DraggableManager {
052            /**
053             * the {@link Container} which contains {@link Component}'s, which may
054             * or may not, implement the {@link Draggable} interface
055             */
056            private Container draggableContainer;
057            /**
058             * maintains whether a "Draggable Container" has been registered or not
059             */
060            private boolean draggableContainerRegistered;
061            /**
062             * the component which was chosen to be dragged
063             */
064            private Draggable hitDraggable;
065            /**
066             * maintains the state of <code>hitDraggable</cdoe>
067             */
068            private byte draggableState;
069            /**
070             * the {@link DragPolicy} to obide by
071             */
072            private DragPolicy dragPolicy;
073            /**
074             * the listener which provides the real ability for a component to
075             * change its location during a "drag"
076             */
077        private DraggableListener dragListener;
078        /**
079         * maintains a {@link java.util.Set} of the components
080         * which have had a {@link DraggableListener} added to them.
081         * This is only used for "cleanup".
082         * This implementation stores the hash code's of each component.
083         */
084        private Set hearingComponents;
085        /**
086         * determines whether the "draggable container" layout manager should be
087         * set to <code>null</code> once a component is dragged (this allows the components
088         * to maintain their position even if the container is resized), or not.
089         */
090        private boolean nullifyLayout = true;
091        
092            /**
093             * Creates a new {@link DraggableManager} with no "Draggable Container"
094             * registered
095             *
096             * @see #DefaultDraggableManager(Container)
097             * @see #registerDraggableContainer(Container)
098             */
099            public DefaultDraggableManager() {
100            }
101            
102            /**
103             * Creates a new {@link DraggableManager} and registers
104             * <code>draggableContainer</code> as the "Draggable Container"
105             * 
106             * @param draggableContainer    the "Draggable Container" to register
107             * 
108             * @throws IllegalArgumentException     if <code>draggableContainer</code> is
109             *                                                                      <code>null</code>
110             * 
111             * @see #DefaultDraggableManager()
112             * @see #registerDraggableContainer(Container)
113             */
114            public DefaultDraggableManager(Container draggableContainer) {
115                    if (draggableContainer == null) {
116                            throw new IllegalArgumentException("Can not register a null Draggable Container");
117                    }
118                    registerDraggableContainer(draggableContainer);
119            }
120    
121            /* (non-Javadoc)
122             * @see com.codecraig.jdraggable.DraggableManager#setNullifyLayout(boolean)
123             */
124            public void setNullifyLayout(boolean nullifyLayout) {
125                    this.nullifyLayout = nullifyLayout;
126            }
127            
128            /* (non-Javadoc)
129             * @see com.codecraig.jdraggable.DraggableManager#shouldNullifyLayout()
130             */
131            public boolean shouldNullifyLayout() {
132                    return nullifyLayout;
133            }
134            
135            /* (non-Javadoc)
136             * @see com.codecraig.jdraggable.DraggableManager#startDrag(java.awt.Component)
137             */
138            public boolean startDrag(Component componentToDrag) {
139                    if (isDraggableContainerRegistered()) {
140                            if (getDragPolicy().isDraggable(componentToDrag)) {
141                                    hitDraggable = (componentToDrag instanceof Draggable ? (Draggable) componentToDrag : new DraggableMask(componentToDrag));
142                                    setState(STATE_STILL);
143                                    return true;
144                            }
145                    }
146                    return false;
147            }
148    
149            /* (non-Javadoc)
150             * @see com.codecraig.jdraggable.DraggableManager#dragging()
151             */
152            public boolean dragging() {
153                    if (isDraggableContainerRegistered()) {
154                            if (hitDraggable != null) {
155                                    setState(STATE_DRAGGING);
156                                    return true;
157                            }
158                    }
159                    return false;
160            }
161            
162            /* (non-Javadoc)
163             * @see com.codecraig.jdraggable.DraggableManager#stopDrag()
164             */
165            public boolean stopDrag() {
166                    if (isDraggableContainerRegistered()) {
167                            hitDraggable = null;
168                            setState(STATE_UNKNOWN);
169                            return true;
170                    }
171                    return false;
172            }
173            
174            /**
175             * Returns the {@link Container} which registered itself as the
176             * "Draggable Container" with this {@link DraggableManager}
177             * 
178             * @return      the "Draggable Container" or <code>null</code> if not
179             *                      <code>Container</code> has been registered as the
180             *                      "Draggable Container"
181             */
182            public Container getDraggableContainer() {
183                    return draggableContainer;
184            }
185            
186            private boolean isDraggableContainerRegistered() {
187                    return draggableContainerRegistered;
188            }
189            
190            /**
191             * Sets the state of <code>hitDraggable</code>
192             * 
193             * @param state the state of <code>hitDraggable</code>
194             */
195            private void setState(byte state) {
196                    draggableState = state;
197            }
198    
199            /**
200             * Returns the state of the current {@link Draggable} component which this
201             * manager is handling
202             * 
203             * @return the state of the current <code>Draggable</code> component
204             *  
205             * @see net.java.swingfx.jdraggable.DraggableManager#getState(net.java.swingfx.jdraggable.Draggable)
206             */
207            public byte getState(Draggable draggableComponent) {
208                    return draggableState;
209            }
210    
211            /**
212             * Registers the given {@link Container} as the "Draggable Container"
213             * 
214             * @param draggableContainer    the <code>Container</code> whose <code>Draggable</code>
215             *                                                              components should be able to be dragged
216             * 
217             * @see DraggableManager#registerDraggableContainer(java.awt.Container)
218             * 
219             * @throws IllegalArgumentException     if a <code>Container</code> has already
220             *                                                                      been registered
221             */
222            public void registerDraggableContainer(Container draggableContainer) {
223                    if (this.draggableContainer == null) {
224                            this.draggableContainer = draggableContainer;
225                            draggableContainer.addContainerListener(this);
226                            dragListener = new DraggableListener(this);
227                            hearingComponents = new HashSet();
228                            draggableContainerRegistered = true;
229                    }
230                    else {
231                            throw new IllegalArgumentException("A Draggable Container has already been registered");
232                    }
233            }
234            
235            /**
236             * Un-Registers the given {@link Container} from being the "Draggable Container"
237             * 
238             * @param draggableContainer    the <code>Container</code> to unregister
239             * 
240             * @see DraggableManager#unregisterDraggableContainer(Container)
241             * 
242             * @throws IllegalArgumentException     if the given container is not the same
243             *                                                                      as the already registered container
244             * @throws IllegalStateException        if no container is currently registered
245             */
246            public void unregisterDraggableContainer(Container draggableContainer) {
247                    if (this.draggableContainer == null) {
248                            throw new IllegalStateException("Failed to unregister draggable container," +
249                                                                                            " since no draggable container was registered");
250                    }
251                    if (this.draggableContainer.equals(draggableContainer)) {
252                            this.draggableContainer.removeContainerListener(this);
253                            cleanupHearingComponents();
254                            this.dragListener = null;
255                            this.draggableContainer = null;
256                            draggableContainerRegistered = false;
257                    }
258                    else {
259                            throw new IllegalArgumentException("Failed to unregister draggable container," +
260                                                                                                    " the given Container is not the same as the" +
261                                                                                                    " register draggable container");                                       
262                    }
263            }
264            
265            /**
266             * Removes the listeners from "hearing components"
267             */
268            private void cleanupHearingComponents() {
269                    int count = draggableContainer.getComponentCount();
270                    for (int i = count - 1; i >= 0 && hearingComponents.size() > 0; i--) {
271                            Component c = draggableContainer.getComponent(i);
272                            Integer code = new Integer(c.hashCode());
273                            if (c != null && hearingComponents.contains(code)) {
274                                    hearingComponents.remove(code);
275                            }
276                    }
277            }
278    
279            /**
280             * Returns the {@link DragPolicy} which this manager obides by
281             * 
282             * @return the <code>DragPolicy</code> for this manager.  If no
283             *                 policy has been set the default policy is used.
284             * 
285             * @see net.java.swingfx.jdraggable.DraggableManager#getDragPolicy()
286             * @see #setDragPolicy(DragPolicy)
287             * @see DragPolicy#DEFAULT
288             */
289            public DragPolicy getDragPolicy() {
290                    if (dragPolicy == null) {
291                            setDragPolicy(DragPolicy.DEFAULT);
292                    }
293                    return dragPolicy;
294            }
295            
296            /* (non-Javadoc)
297             * @see com.codecraig.jdraggable.DraggableManager#setDragPolicy(com.codecraig.jdraggable.DragPolicy)
298             */
299            public void setDragPolicy(DragPolicy dragPolicy) {
300                    this.dragPolicy = dragPolicy;
301            }
302            
303            /* (non-Javadoc)
304             * @see java.awt.event.ContainerListener#componentAdded(java.awt.event.ContainerEvent)
305             */
306            public void componentAdded(ContainerEvent e) {
307                    if (dragListener == null || isDraggableContainerRegistered() == false) {
308                            // this should not occur, since we listening to a container in the first place
309                            throw new IllegalStateException("Draggable Container must be registered prior to adding components");
310                    }
311                    Component c = e.getChild();
312                    Integer code = new Integer(c.hashCode());
313                    if (hearingComponents.contains(code) == false) {
314                            hearingComponents.add(code);
315                            c.addMouseListener(dragListener);
316                            c.addMouseMotionListener(dragListener); 
317                    }
318            }
319            
320            /* (non-Javadoc)
321             * @see java.awt.event.ContainerListener#componentRemoved(java.awt.event.ContainerEvent)
322             */
323            public void componentRemoved(ContainerEvent e) {
324                    Component c = e.getChild();
325                    Integer code = new Integer(c.hashCode());
326                    if (hearingComponents.contains(code)) {
327                            hearingComponents.remove(code);
328                    }
329            }
330    }