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}