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