001/*
002 * $Id: ToggleActionPropertyChangeListener.java 3972 2011-03-17 20:31:58Z kschaefe $
003 *
004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 * 
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 * Lesser General Public License for more details.
016 * 
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020 */
021package org.jdesktop.swingx.action;
022
023import java.beans.PropertyChangeEvent;
024import java.beans.PropertyChangeListener;
025import java.lang.ref.WeakReference;
026
027import javax.swing.AbstractAction;
028import javax.swing.AbstractButton;
029import javax.swing.Action;
030
031/**
032 * Added to the Toggle type buttons and menu items so that various components
033 * which have been created from a single StateChangeAction can be in synch. 
034 * 
035 * This listener is responsible for updating the selected property from the 
036 * Action to the AbstractButton. <p>
037 * 
038 * It guarantees a maximum of 1 instance of 
039 * ToggleActionPCL to be installed per button (PENDING JW: add test to verify). 
040 * It removes all ToggleActionPCLs which are targeted to unreachable buttons
041 * from the action's listener list.
042 * 
043 * 
044 */
045class ToggleActionPropertyChangeListener implements PropertyChangeListener {
046
047
048    private WeakReference<AbstractButton> buttonRef;
049    
050    public ToggleActionPropertyChangeListener(Action action, AbstractButton button) {
051        if (shouldAddListener(action, button)) {
052            this.buttonRef = new WeakReference<AbstractButton>(button);
053            action.addPropertyChangeListener(this);
054        }
055    }
056
057    
058    protected synchronized boolean shouldAddListener(Action action, AbstractButton button) {
059        releasePCLs(action);
060        // PENDING JW: revisit - we need a configurator to maintain at most a 1:1 from button to
061        // action anyway: so a true in isToggling must not happen.
062        // 
063        return !isToggling(action, button);
064//        return true;
065    }
066
067
068    protected boolean isToggling(Action action, AbstractButton button) {
069        if (!(action instanceof AbstractAction)) return false;
070        PropertyChangeListener[] listeners = ((AbstractAction)action).getPropertyChangeListeners();
071        for (int i = listeners.length - 1; i >= 0; i--) {
072            if (listeners[i] instanceof ToggleActionPropertyChangeListener) {
073                if (((ToggleActionPropertyChangeListener) listeners[i]).isToggling(button)) return true;
074            }
075        }
076        return false;
077    }
078
079    /**
080     * Removes all ToggleActionPCLs with unreachable target buttons from the 
081     * Action's PCL-listeners.
082     * 
083     * @param action to cleanup.
084     */
085    protected void releasePCLs(Action action) {
086        if (!(action instanceof AbstractAction)) return;
087        PropertyChangeListener[] listeners = ((AbstractAction)action).getPropertyChangeListeners();
088        for (int i = listeners.length - 1; i >= 0; i--) {
089            if (listeners[i] instanceof ToggleActionPropertyChangeListener) {
090                ((ToggleActionPropertyChangeListener) listeners[i]).checkReferent(action);
091            }
092        }
093    }
094
095    
096    @Override
097    public void propertyChange(PropertyChangeEvent evt) {
098        AbstractButton button = checkReferent((Action) evt.getSource());
099        if (button == null) return;
100        String propertyName = evt.getPropertyName();
101
102        if (propertyName.equals("selected")) {
103            Boolean selected = (Boolean)evt.getNewValue();
104            button.setSelected(selected.booleanValue());
105        }
106    }
107
108    /**
109     * Returns the target button to synchronize from the listener.
110     * 
111     * Side-effects if the target is no longer reachable:
112     *  - the internal reference to target is nulled.
113     *  - if the given action is != null, this listener removes 
114     *       itself from the action's listener list.
115     * 
116     * @param action The action this is listening to.
117     * @return the target button if it is strongly reachable or null 
118     *    if it is no longer strongly reachable.
119     */
120    protected AbstractButton checkReferent(Action action) {
121        AbstractButton button = null;
122        if (buttonRef != null) {
123            button = buttonRef.get();
124        }
125        if (button == null) {
126            if (action != null) {
127                action.removePropertyChangeListener(this);
128            }
129           buttonRef = null;
130        }
131        return button;
132    }
133
134
135    /**
136     * Check if this is already synchronizing the given AbstractButton.
137     * 
138     * This may have the side-effect of releasing the weak reference to
139     * the target button.
140     * 
141     * @param button must not be null
142     * @return true if this target button and the given comp are equal 
143     *         false otherwise. 
144     * @throws NullPointerException if the button is null.
145     */
146    public boolean isToggling(AbstractButton button) {
147        // JW: should check identity instead of equality?
148        return button.equals(checkReferent(null));
149    }
150
151    /**
152     * Checks if this is synchronizing to the same target button as the
153     * given listener.
154     * 
155     * This may have the side-effect of releasing the weak reference to
156     * the target button.
157     * 
158     * @param pcl The listener to test, must not be null.
159     * @return true if the target buttons of the given is equal to this target
160     *    button and the button is strongly reachable, false otherwise.
161     */
162//    public boolean isToggling(ToggleActionPropertyChangeListener pcl) {
163//        AbstractButton other = pcl.checkReferent(null);
164//        if (other != null) {
165//            return isToggling(other);
166//        }
167//        return false;
168//    }
169    
170    
171}