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 }