001 /* 002 * $Id: RolloverController.java 3100 2008-10-14 22:33:10Z rah003 $ 003 * 004 * Copyright 2006 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 */ 021 package org.jdesktop.swingx.rollover; 022 023 import java.awt.Point; 024 import java.awt.event.ActionEvent; 025 import java.beans.PropertyChangeEvent; 026 import java.beans.PropertyChangeListener; 027 import java.util.logging.Logger; 028 029 import javax.swing.AbstractAction; 030 import javax.swing.Action; 031 import javax.swing.JComponent; 032 import javax.swing.KeyStroke; 033 034 /** 035 * Controller for "live" behaviour of XXRenderers. 036 * 037 * Once installed on a component, it updates renderer's rollover 038 * state based on the component's rollover properties. Rollover 039 * client properties are Points with cell coordinates 040 * in the view coordinate 041 * system as approriate for the concrete component 042 * (Point.x == column, Point.y == row). 043 * 044 * Repaints effected component regions. Updates 045 * link cursor. Installs a click-action bound to space-released in the target's 046 * actionMap/inputMap. 047 * 048 * 049 * @author Jeanette Winzenburg, Berlin 050 */ 051 public abstract class RolloverController<T extends JComponent> implements 052 PropertyChangeListener { 053 @SuppressWarnings("unused") 054 private static final Logger LOG = Logger.getLogger(RolloverController.class 055 .getName()); 056 /** 057 * the key of the rollover click action which is installed in the 058 * component's actionMap. 059 */ 060 public static final String EXECUTE_BUTTON_ACTIONCOMMAND = "executeButtonAction"; 061 062 protected T component; 063 064 public void propertyChange(PropertyChangeEvent evt) { 065 // JW: should not happen ... being paranoid. 066 if ((component == null) || (component != evt.getSource())) 067 return; 068 if (RolloverProducer.ROLLOVER_KEY.equals(evt.getPropertyName())) { 069 rollover((Point) evt.getOldValue(), (Point) evt.getNewValue()); 070 } else if (RolloverProducer.CLICKED_KEY.equals(evt.getPropertyName())) { 071 click((Point) evt.getNewValue()); 072 } 073 } 074 075 /** 076 * Install this as controller for the given component. 077 * 078 * @param table the component which has renderers to control. 079 */ 080 public void install(T table) { 081 release(); 082 this.component = table; 083 table.addPropertyChangeListener(RolloverProducer.CLICKED_KEY, this); 084 table.addPropertyChangeListener(RolloverProducer.ROLLOVER_KEY, this); 085 registerExecuteButtonAction(); 086 } 087 088 /** 089 * Uninstall this as controller from the component, if any. 090 * 091 */ 092 public void release() { 093 if (component == null) 094 return; 095 component.removePropertyChangeListener(RolloverProducer.CLICKED_KEY, this); 096 component.removePropertyChangeListener(RolloverProducer.ROLLOVER_KEY, this); 097 unregisterExecuteButtonAction(); 098 component = null; 099 } 100 101 /** 102 * called on change of client property Rollover_Key. 103 * 104 * @param oldLocation the old value of the rollover location. 105 * @param newLocation the new value of the rollover location. 106 */ 107 protected abstract void rollover(Point oldLocation, Point newLocation); 108 109 /** 110 * called on change of client property Clicked_key. 111 * @param location the new value of the clicked location. 112 */ 113 protected void click(Point location) { 114 if (!isClickable(location)) 115 return; 116 RolloverRenderer rollover = getRolloverRenderer(location, true); 117 if (rollover != null) { 118 rollover.doClick(); 119 component.repaint(); 120 } 121 } 122 123 /** 124 * Returns the rolloverRenderer at the given location. <p> 125 * 126 * The result 127 * may be null if there is none or if rollover is not enabled. 128 * 129 * If the prepare flag is true, the renderer will be prepared 130 * with value and state as appropriate for the given location. 131 * 132 * Note: PRE - the location must be valid in cell coordinate space. 133 * 134 * @param location a valid location in cell coordinates, p.x == column, p.y == row. 135 * @param prepare 136 * @return <code>RolloverRenderer</code> at the given location 137 */ 138 protected abstract RolloverRenderer getRolloverRenderer(Point location, 139 boolean prepare); 140 141 /** 142 * checks and returns if the cell at the given location is 143 * clickable. Here: same as isRolloverCell. 144 * 145 * @param location in cell coordinates, p.x == column, p.y == row. 146 * 147 * @return true if the cell at the given location is clickable 148 */ 149 protected boolean isClickable(Point location) { 150 return hasRollover(location); 151 } 152 153 /** 154 * checks and returns if the cell at the given location has 155 * rollover effects. <p> 156 * 157 * Always returns false if the location 158 * is not valid. 159 * 160 * 161 * @param location in cell coordinates, p.x == column, p.y == row. 162 * 163 * @return true if the location is valid and has rollover effects, false 164 * otherwise. 165 * 166 */ 167 protected boolean hasRollover(Point location) { 168 if (location == null || location.x < 0 || location.y < 0) 169 return false; 170 return getRolloverRenderer(location, false) != null; 171 } 172 173 /** 174 * The coordinates of the focused cell in view coordinates. 175 * 176 * This method is called if the click action is invoked by a keyStroke. 177 * The returned cell coordinates should be related to 178 * what is typically interpreted as "focused" in the context of the 179 * component. 180 * 181 * p.x == focused column, p.y == focused row. 182 * A null return value or any coordinate value of < 0 183 * is interpreted as "outside". 184 * 185 * @return the location of the focused cell. 186 */ 187 protected abstract Point getFocusedCell(); 188 189 /** 190 * uninstalls and deregisters the click action from the component's 191 * actionMap/inputMap. 192 * 193 */ 194 protected void unregisterExecuteButtonAction() { 195 component.getActionMap().put(EXECUTE_BUTTON_ACTIONCOMMAND, null); 196 KeyStroke space = KeyStroke.getKeyStroke("released SPACE"); 197 component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put( 198 space, null); 199 } 200 201 /** 202 * installs and registers the click action in the component's 203 * actionMap/inputMap. 204 * 205 */ 206 protected void registerExecuteButtonAction() { 207 component.getActionMap().put(EXECUTE_BUTTON_ACTIONCOMMAND, 208 createExecuteButtonAction()); 209 KeyStroke space = KeyStroke.getKeyStroke("released SPACE"); 210 component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put( 211 space, EXECUTE_BUTTON_ACTIONCOMMAND); 212 213 } 214 215 /** 216 * creates and returns the click action to install in the 217 * component's actionMap. 218 * 219 */ 220 protected Action createExecuteButtonAction() { 221 return new AbstractAction() { 222 public void actionPerformed(ActionEvent e) { 223 click(getFocusedCell()); 224 } 225 226 @Override 227 public boolean isEnabled() { 228 if (component == null || !component.isEnabled() || !component.hasFocus()) 229 return false; 230 return isClickable(getFocusedCell()); 231 } 232 }; 233 } 234 235 }