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 }