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 }