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 }