001 /* 002 * $Id: HyperlinkProvider.java 3294 2009-03-11 10:50:52Z 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 */ 021 package org.jdesktop.swingx.renderer; 022 023 import java.awt.Point; 024 import java.awt.event.ActionEvent; 025 026 import org.jdesktop.swingx.JXHyperlink; 027 import org.jdesktop.swingx.hyperlink.AbstractHyperlinkAction; 028 import org.jdesktop.swingx.rollover.RolloverProducer; 029 import 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 */ 059 public 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 public void actionPerformed(ActionEvent e) { 172 // TODO Auto-generated method stub 173 174 } 175 176 }; 177 } 178 179 //----------------------- Implement RolloverRenderer 180 181 public boolean isEnabled() { 182 return true; 183 } 184 185 public void doClick() { 186 rendererComponent.doClick(); 187 } 188 189 //------------------------ ComponentProvider 190 191 /** 192 * {@inheritDoc} <p> 193 * 194 * PENDING JW: Needs to be overridden - doesn't comply to contract!. Not sure 195 * how to do it without disturbing the hyperlinks current setting? 196 * All hyperlink properties are defined by the LinkAction configured 197 * with the target ... 198 */ 199 @Override 200 public String getString(Object value) { 201 if (isTargetable(value)) { 202 Object oldTarget = linkAction.getTarget(); 203 linkAction.setTarget(value); 204 String text = linkAction.getName(); 205 linkAction.setTarget(oldTarget); 206 return text; 207 } 208 return super.getString(value); 209 } 210 211 212 213 /** 214 * {@inheritDoc} <p> 215 * 216 * Overridden to set the hyperlink's rollover state. 217 */ 218 @Override 219 protected void configureState(CellContext context) { 220 // rendererComponent.setHorizontalAlignment(getHorizontalAlignment()); 221 if (context.getComponent() != null) { 222 Point p = (Point) context.getComponent() 223 .getClientProperty(RolloverProducer.ROLLOVER_KEY); 224 if (/*hasFocus || */(p != null && (p.x >= 0) && 225 (p.x == context.getColumn()) && (p.y == context.getRow()))) { 226 if (!rendererComponent.getModel().isRollover()) 227 rendererComponent.getModel().setRollover(true); 228 } else { 229 if (rendererComponent.getModel().isRollover()) 230 rendererComponent.getModel().setRollover(false); 231 } 232 } 233 } 234 235 /** 236 * {@inheritDoc} 237 * 238 * Overridden to set the LinkAction's target to the context's value, if 239 * targetable.<p> 240 * 241 * Forces foreground color to the one defined by hyperlink for unselected 242 * cells, doesn't change the foreground for selected (as darkish text on dark selection 243 * background might be unreadable, Issue #840-swingx). Not entirely safe because 244 * the unselected background might be dark as well. Need to find a better way in 245 * the long run. Until then, client code can use Highlighters to repair 246 * (which is nasty!). <p> 247 * 248 * PENDING JW: by-passes XXValues - state currently is completely defined by 249 * the action. Hmm ... 250 * 251 */ 252 @Override 253 protected void format(CellContext context) { 254 Object value = context.getValue(); 255 if (isTargetable(value)) { 256 linkAction.setTarget(value); 257 } else { 258 linkAction.setTarget(null); 259 } 260 // hmm... the hyperlink should do this automatically.. 261 // Issue #840-swingx: hyperlink unreadable if selected (for dark selection colors) 262 // so we only force clicked/unclicked if unselected 263 if (!context.isSelected()) { 264 rendererComponent.setForeground(linkAction.isVisited() ? 265 rendererComponent.getClickedColor() : rendererComponent.getUnclickedColor()); 266 } else { 267 // JW: workaround #845-swingx which was introduced by fixing #840 268 // if we interfere with the colors, need to do always. Not quite understood 269 rendererComponent.setForeground(context.getSelectionForeground()); 270 } 271 } 272 273 /** 274 * {@inheritDoc} 275 */ 276 @Override 277 protected JXHyperlink createRendererComponent() { 278 return new JXRendererHyperlink(); 279 } 280 281 282 }