001/* 002 * $Id: HyperlinkProvider.java 3927 2011-02-22 16:34:11Z kleopatra $ 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 */ 021package org.jdesktop.swingx.renderer; 022 023import java.awt.Point; 024import java.awt.event.ActionEvent; 025 026import org.jdesktop.swingx.JXHyperlink; 027import org.jdesktop.swingx.hyperlink.AbstractHyperlinkAction; 028import org.jdesktop.swingx.rollover.RolloverProducer; 029import org.jdesktop.swingx.rollover.RolloverRenderer; 030 031/** 032 * Renderer for hyperlinks". <p> 033 * 034 * The renderer is configured with a LinkAction<T>. 035 * It's mostly up to the developer to guarantee that the all 036 * values which are passed into the getXXRendererComponent(...) are 037 * compatible with T: she can provide a runtime class to check against. 038 * If it isn't the renderer will configure the 039 * action with a null target. <p> 040 * 041 * It's recommended to not use the given Action anywhere else in code, 042 * as it is updated on each getXXRendererComponent() call which might 043 * lead to undesirable side-effects. <p> 044 * 045 * Internally uses JXHyperlink as rendering component. <p> 046 * 047 * PENDING: can go from ButtonProvider? <p> 048 * 049 * PENDING: make renderer respect selected cell state. <p> 050 * 051 * PENDING: TreeCellRenderer has several issues <p> 052 * <ol> 053 * <li> no icons 054 * <li> usual background highlighter issues 055 * </ol> 056 * 057 * @author Jeanette Winzenburg 058 */ 059public class HyperlinkProvider 060 extends ComponentProvider<JXHyperlink> implements 061 RolloverRenderer { 062 063 064 private AbstractHyperlinkAction<Object> linkAction; 065 protected Class<?> targetClass; 066 067 /** 068 * Instantiate a LinkRenderer with null LinkAction and null 069 * targetClass. 070 * 071 */ 072 public HyperlinkProvider() { 073 this(null, null); 074 } 075 076 /** 077 * Instantiate a LinkRenderer with the LinkAction to use with 078 * target values. 079 * 080 * @param linkAction the action that acts on values. 081 */ 082 public HyperlinkProvider(AbstractHyperlinkAction linkAction) { 083 this(linkAction, null); 084 } 085 086 /** 087 * Instantiate a LinkRenderer with a LinkAction to use with 088 * target values and the type of values the action can cope with. <p> 089 * 090 * It's up to developers to take care of matching types. 091 * 092 * @param linkAction the action that acts on values. 093 * @param targetClass the type of values the action can handle. 094 */ 095 public HyperlinkProvider(AbstractHyperlinkAction linkAction, Class<?> targetClass) { 096 super(); 097// rendererComponent.addActionListener(createEditorActionListener()); 098 setLinkAction(linkAction, targetClass); 099 } 100 101 /** 102 * Sets the class the action is supposed to handle. <p> 103 * 104 * PENDING: make sense to set independently of LinkAction? 105 * 106 * @param targetClass the type of values the action can handle. 107 */ 108 public void setTargetClass(Class<?> targetClass) { 109 this.targetClass = targetClass; 110 } 111 112 /** 113 * Sets the LinkAction for handling the values. <p> 114 * 115 * The action is assumed to be able to cope with any type, that is 116 * this method is equivalent to setLinkAction(linkAction, null). 117 * 118 * @param linkAction 119 */ 120 public void setLinkAction(AbstractHyperlinkAction linkAction) { 121 setLinkAction(linkAction, null); 122 } 123 124 /** 125 * Sets the LinkAction for handling the values and the 126 * class the action can handle. <p> 127 * 128 * PENDING: in the general case this is not independent of the 129 * targetClass. Need api to set them combined? 130 * 131 * @param linkAction 132 */ 133 public void setLinkAction(AbstractHyperlinkAction linkAction, Class<?> targetClass) { 134 if (linkAction == null) { 135 linkAction = createDefaultLinkAction(); 136 } 137 setTargetClass(targetClass); 138 this.linkAction = linkAction; 139 rendererComponent.setAction(linkAction); 140 141 } 142 /** 143 * decides if the given target is acceptable for setTarget. 144 * <p> 145 * 146 * target == null is acceptable for all types. 147 * targetClass == null is the same as Object.class 148 * 149 * @param target the target to set. 150 * @return true if setTarget can cope with the object, 151 * false otherwise. 152 * 153 */ 154 public boolean isTargetable(Object target) { 155 // we accept everything 156 if (targetClass == null) return true; 157 if (target == null) return true; 158 return targetClass.isAssignableFrom(target.getClass()); 159 } 160 161 162 163 /** 164 * default action - does nothing... except showing the target. 165 * 166 * @return a default LinkAction for showing the target. 167 */ 168 protected AbstractHyperlinkAction createDefaultLinkAction() { 169 return new AbstractHyperlinkAction<Object>(null) { 170 171 @Override 172 public void actionPerformed(ActionEvent e) { 173 // TODO Auto-generated method stub 174 175 } 176 177 }; 178 } 179 180//----------------------- Implement RolloverRenderer 181 182 @Override 183 public boolean isEnabled() { 184 return true; 185 } 186 187 @Override 188 public void doClick() { 189 rendererComponent.doClick(); 190 } 191 192//------------------------ ComponentProvider 193 194 /** 195 * {@inheritDoc} <p> 196 * 197 * PENDING JW: Needs to be overridden - doesn't comply to contract!. Not sure 198 * how to do it without disturbing the hyperlinks current setting? 199 * All hyperlink properties are defined by the LinkAction configured 200 * with the target ... 201 */ 202 @Override 203 public String getString(Object value) { 204 if (isTargetable(value)) { 205 Object oldTarget = linkAction.getTarget(); 206 linkAction.setTarget(value); 207 String text = linkAction.getName(); 208 linkAction.setTarget(oldTarget); 209 return text; 210 } 211 return super.getString(value); 212 } 213 214 215 216 /** 217 * {@inheritDoc} <p> 218 * 219 * Overridden to set the hyperlink's rollover state. 220 */ 221 @Override 222 protected void configureState(CellContext context) { 223// rendererComponent.setHorizontalAlignment(getHorizontalAlignment()); 224 if (context.getComponent() != null) { 225 Point p = (Point) context.getComponent() 226 .getClientProperty(RolloverProducer.ROLLOVER_KEY); 227 if (/*hasFocus || */(p != null && (p.x >= 0) && 228 (p.x == context.getColumn()) && (p.y == context.getRow()))) { 229 if (!rendererComponent.getModel().isRollover()) 230 rendererComponent.getModel().setRollover(true); 231 } else { 232 if (rendererComponent.getModel().isRollover()) 233 rendererComponent.getModel().setRollover(false); 234 } 235 } 236 } 237 238 /** 239 * {@inheritDoc} 240 * 241 * Overridden to set the LinkAction's target to the context's value, if 242 * targetable.<p> 243 * 244 * Forces foreground color to the one defined by hyperlink for unselected 245 * cells, doesn't change the foreground for selected (as darkish text on dark selection 246 * background might be unreadable, Issue #840-swingx). Not entirely safe because 247 * the unselected background might be dark as well. Need to find a better way in 248 * the long run. Until then, client code can use Highlighters to repair 249 * (which is nasty!). <p> 250 * 251 * PENDING JW: by-passes XXValues - state currently is completely defined by 252 * the action. Hmm ... 253 * 254 */ 255 @Override 256 protected void format(CellContext context) { 257 Object value = context.getValue(); 258 if (isTargetable(value)) { 259 linkAction.setTarget(value); 260 } else { 261 linkAction.setTarget(null); 262 } 263 // hmm... the hyperlink should do this automatically.. 264 // Issue #840-swingx: hyperlink unreadable if selected (for dark selection colors) 265 // so we only force clicked/unclicked if unselected 266 if (!context.isSelected()) { 267 rendererComponent.setForeground(linkAction.isVisited() ? 268 rendererComponent.getClickedColor() : rendererComponent.getUnclickedColor()); 269 } else { 270 // JW: workaround #845-swingx which was introduced by fixing #840 271 // if we interfere with the colors, need to do always. Not quite understood 272 rendererComponent.setForeground(context.getSelectionForeground()); 273 } 274 } 275 276 /** 277 * {@inheritDoc} 278 */ 279 @Override 280 protected JXHyperlink createRendererComponent() { 281 return new JXRendererHyperlink(); 282 } 283 284 285}