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