001 /*
002 * $Id: LinkRenderer.java,v 1.22 2006/04/12 15:06:17 kleopatra Exp $
003 *
004 * Copyright 2004 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;
022
023 import java.awt.Component;
024 import java.awt.Point;
025 import java.awt.event.ActionEvent;
026 import java.awt.event.ActionListener;
027
028 import javax.swing.AbstractCellEditor;
029 import javax.swing.JList;
030 import javax.swing.JTable;
031 import javax.swing.JTree;
032 import javax.swing.ListCellRenderer;
033 import javax.swing.UIManager;
034 import javax.swing.border.Border;
035 import javax.swing.border.EmptyBorder;
036 import javax.swing.table.TableCellEditor;
037 import javax.swing.table.TableCellRenderer;
038 import javax.swing.tree.TreeCellRenderer;
039
040 import org.jdesktop.swingx.action.LinkAction;
041
042 /**
043 * A Renderer/Editor for "Links". <p>
044 *
045 * The renderer is configured with a LinkAction<T>.
046 * It's mostly up to the developer to guarantee that the all
047 * values which are passed into the getXXRendererComponent(...) are
048 * compatible with T: she can provide a runtime class to check against.
049 * If it isn't the renderer will configure the
050 * action with a null target. <p>
051 *
052 * It's recommended to not use the given Action anywhere else in code,
053 * as it is updated on each getXXRendererComponent() call which might
054 * lead to undesirable side-effects. <p>
055 *
056 * Internally uses JXHyperlink for both CellRenderer and CellEditor
057 * It's recommended to not reuse the same instance for both functions. <p>
058 *
059 * PENDING: make renderer respect selected cell state.
060 *
061 * PENDING: TreeCellRenderer has several issues
062 * - no icons
063 * - usual background highlighter issues
064 *
065 * @author Jeanette Winzenburg
066 */
067 public class LinkRenderer extends AbstractCellEditor implements
068 TableCellRenderer, TableCellEditor, ListCellRenderer,
069 TreeCellRenderer, RolloverRenderer {
070
071 private static final Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);
072
073 private JXHyperlink linkButton;
074
075 private LinkAction<Object> linkAction;
076 protected Class<?> targetClass;
077
078 /**
079 * Instantiate a LinkRenderer with null LinkAction and null
080 * targetClass.
081 *
082 */
083 public LinkRenderer() {
084 this(null, null);
085 }
086
087 /**
088 * Instantiate a LinkRenderer with the LinkAction to use with
089 * target values.
090 *
091 * @param linkAction the action that acts on values.
092 */
093 public LinkRenderer(LinkAction linkAction) {
094 this(linkAction, null);
095 }
096
097 /**
098 * Instantiate a LinkRenderer with a LinkAction to use with
099 * target values and the type of values the action can cope with. <p>
100 *
101 * It's up to developers to take care of matching types.
102 *
103 * @param linkAction the action that acts on values.
104 * @param targetClass the type of values the action can handle.
105 */
106 public LinkRenderer(LinkAction linkAction, Class targetClass) {
107 linkButton = createHyperlink();
108 linkButton.addActionListener(createEditorActionListener());
109 setLinkAction(linkAction, targetClass);
110 }
111
112 /**
113 * Sets the class the action is supposed to handle. <p>
114 *
115 * PENDING: make sense to set independently of LinkAction?
116 *
117 * @param targetClass the type of values the action can handle.
118 */
119 public void setTargetClass(Class targetClass) {
120 this.targetClass = targetClass;
121 }
122
123 /**
124 * Sets the LinkAction for handling the values. <p>
125 *
126 * The action is assumed to be able to cope with any type, that is
127 * this method is equivalent to setLinkAction(linkAction, null).
128 *
129 * @param linkAction
130 */
131 public void setLinkAction(LinkAction linkAction) {
132 setLinkAction(linkAction, null);
133 }
134
135 /**
136 * Sets the LinkAction for handling the values and the
137 * class the action can handle. <p>
138 *
139 * PENDING: in the general case this is not independent of the
140 * targetClass. Need api to set them combined?
141 *
142 * @param linkAction
143 */
144 public void setLinkAction(LinkAction linkAction, Class targetClass) {
145 if (linkAction == null) {
146 linkAction = createDefaultLinkAction();
147 }
148 setTargetClass(targetClass);
149 this.linkAction = linkAction;
150 linkButton.setAction(linkAction);
151
152 }
153 /**
154 * decides if the given target is acceptable for setTarget.
155 * <p>
156 *
157 * target == null is acceptable for all types.
158 * targetClass == null is the same as Object.class
159 *
160 * @param target the target to set.
161 * @return true if setTarget can cope with the object,
162 * false otherwise.
163 *
164 */
165 public boolean isTargetable(Object target) {
166 // we accept everything
167 if (targetClass == null) return true;
168 if (target == null) return true;
169 return targetClass.isAssignableFrom(target.getClass());
170 }
171
172
173 /**
174 * creates and returns the hyperlink component used for rendering
175 * the value and activating the action on the target value.
176 *
177 * @return the hyperlink renderer component.
178 */
179 protected JXHyperlink createHyperlink() {
180 return new JXHyperlink() {
181
182 @Override
183 public void updateUI() {
184 super.updateUI();
185 setBorderPainted(true);
186 setOpaque(true);
187 }
188
189 };
190 }
191
192 /**
193 * default action - does nothing... except showing the target.
194 *
195 * @return a default LinkAction for showing the target.
196 */
197 protected LinkAction createDefaultLinkAction() {
198 return new LinkAction<Object>(null) {
199
200 public void actionPerformed(ActionEvent e) {
201 // TODO Auto-generated method stub
202
203 }
204
205 };
206 }
207
208 //----------------------- Implement RolloverRenderer
209
210 public boolean isEnabled() {
211 return true;
212 }
213
214 public void doClick() {
215 linkButton.doClick();
216 }
217
218 //---------------------- Implement ListCellRenderer
219
220 public Component getListCellRendererComponent(JList list, Object value,
221 int index, boolean isSelected, boolean cellHasFocus) {
222 if ((value != null) && !isTargetable(value)) {
223 value = null;
224 }
225 linkAction.setTarget(value);
226 if (list != null) {
227 Point p = (Point) list
228 .getClientProperty(RolloverProducer.ROLLOVER_KEY);
229 if (/*cellHasFocus ||*/ (p != null && (p.y >= 0) && (p.y == index))) {
230 linkButton.getModel().setRollover(true);
231 } else {
232 linkButton.getModel().setRollover(false);
233 }
234 updateSelectionColors(list, isSelected);
235 updateFocusBorder(cellHasFocus);
236 };
237 return linkButton;
238 }
239
240
241 private void updateSelectionColors(JList table, boolean isSelected) {
242 if (isSelected) {
243 // linkButton.setForeground(table.getSelectionForeground());
244 linkButton.setBackground(table.getSelectionBackground());
245 } else {
246 // linkButton.setForeground(table.getForeground());
247 linkButton.setBackground(table.getBackground());
248 }
249
250 }
251
252 //------------------------ TableCellRenderer
253
254 public Component getTableCellRendererComponent(JTable table, Object value,
255 boolean isSelected, boolean hasFocus, int row, int column) {
256 if ((value != null) && !isTargetable(value)) {
257 value = null;
258 }
259 linkAction.setTarget(value);
260 if (table != null) {
261 Point p = (Point) table
262 .getClientProperty(RolloverProducer.ROLLOVER_KEY);
263 if (/*hasFocus || */(p != null && (p.x >= 0) && (p.x == column) && (p.y == row))) {
264 linkButton.getModel().setRollover(true);
265 } else {
266 linkButton.getModel().setRollover(false);
267 }
268 updateSelectionColors(table, isSelected);
269 updateFocusBorder(hasFocus);
270 }
271 return linkButton;
272 }
273
274 private void updateSelectionColors(JTable table, boolean isSelected) {
275 if (isSelected) {
276 // linkButton.setForeground(table.getSelectionForeground());
277 linkButton.setBackground(table.getSelectionBackground());
278 }
279 else {
280 // linkButton.setForeground(table.getForeground());
281 linkButton.setBackground(table.getBackground());
282 }
283
284 }
285
286 private void updateFocusBorder(boolean hasFocus) {
287 if (hasFocus) {
288 linkButton.setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
289 } else {
290 linkButton.setBorder(noFocusBorder);
291 }
292
293
294 }
295
296 //-------------------------- TableCellEditor
297
298 public Component getTableCellEditorComponent(JTable table, Object value,
299 boolean isSelected, int row, int column) {
300 linkAction.setTarget(value);
301 linkButton.getModel().setRollover(true);
302 updateSelectionColors(table, isSelected);
303 return linkButton;
304 }
305
306 public Object getCellEditorValue() {
307 return linkAction.getTarget();
308 }
309
310
311
312 @Override
313 protected void fireEditingStopped() {
314 fireEditingCanceled();
315 }
316
317 private ActionListener createEditorActionListener() {
318 ActionListener l = new ActionListener() {
319
320 public void actionPerformed(ActionEvent e) {
321 cancelCellEditing();
322
323 }
324
325 };
326 return l;
327 }
328
329 //----------------------- treeCellRenderer
330
331 public Component getTreeCellRendererComponent(JTree tree, Object value, boolean isSelected,
332 boolean expanded, boolean leaf, int row, boolean hasFocus) {
333 if ((value != null) && !isTargetable(value)) {
334 value = null;
335 }
336 linkAction.setTarget(value);
337 if (tree != null) {
338 Point p = (Point) tree
339 .getClientProperty(RolloverProducer.ROLLOVER_KEY);
340 if (/*cellHasFocus ||*/ (p != null && (p.y >= 0) && (p.y == row))) {
341 linkButton.getModel().setRollover(true);
342 } else {
343 linkButton.getModel().setRollover(false);
344 }
345 updateSelectionColors(tree, isSelected);
346 updateFocusBorder(hasFocus);
347 }
348 return linkButton;
349
350 }
351
352 private void updateSelectionColors(JTree tree, boolean isSelected) {
353 if (isSelected) {
354 // linkButton.setForeground(table.getSelectionForeground());
355 linkButton.setBackground(UIManager.getColor("Tree.selectionBackground"));
356 }
357 else {
358 // linkButton.setForeground(table.getForeground());
359 linkButton.setBackground(tree.getBackground());
360 }
361
362 }
363
364 }